feat: bundle syncing server into home server setup (#611)

* feat(syncing-server): move inversify express controllers to new structure

* wip: syncing server service binding for home server

* fix(syncing-server): container bindings

* fix(api-gateway): rename https service to service proxy

* fix: proxying requests to syncing server

* fix: responses and version binding
This commit is contained in:
Karol Sójko 2023-05-17 15:38:12 +02:00 committed by GitHub
parent 18a5071618
commit b3b617ea0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 780 additions and 674 deletions

11
.pnp.cjs generated
View file

@ -4626,6 +4626,7 @@ const RAW_RUNTIME_STATE =
["@standardnotes/auth-server", "workspace:packages/auth"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/syncing-server", "workspace:packages/syncing-server"],\
["@types/cors", "npm:2.8.13"],\
["@types/express", "npm:4.17.17"],\
["@types/prettyjson", "npm:0.0.30"],\
@ -4732,7 +4733,7 @@ const RAW_RUNTIME_STATE =
["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.15"],\
["typeorm", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:0.3.15"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
["winston", "npm:3.8.2"]\
],\
@ -4924,7 +4925,7 @@ const RAW_RUNTIME_STATE =
["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.15"],\
["typeorm", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:0.3.15"],\
["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"],\
@ -15165,10 +15166,10 @@ const RAW_RUNTIME_STATE =
],\
"linkType": "HARD"\
}],\
["virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.15", {\
"packageLocation": "./.yarn/__virtual__/typeorm-virtual-7fe891193c/0/cache/typeorm-npm-0.3.15-20a6c4f754-db890f14cb.zip/node_modules/typeorm/",\
["virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:0.3.15", {\
"packageLocation": "./.yarn/__virtual__/typeorm-virtual-91f15b21d5/0/cache/typeorm-npm-0.3.15-20a6c4f754-db890f14cb.zip/node_modules/typeorm/",\
"packageDependencies": [\
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.15"],\
["typeorm", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:0.3.15"],\
["@google-cloud/spanner", null],\
["@sap/hana-client", null],\
["@sqltools/formatter", "npm:1.2.5"],\

View file

@ -1,28 +1,14 @@
import {
ControllerContainerInterface,
ServiceContainerInterface,
ServiceIdentifier,
ServiceInterface,
} from '@standardnotes/domain-core'
import { ServiceContainerInterface, ServiceIdentifier, ServiceInterface } from '@standardnotes/domain-core'
import { ContainerConfigLoader } from './Container'
export class Service implements ServiceInterface {
constructor(
private serviceContainer: ServiceContainerInterface,
private controllerContainer: ControllerContainerInterface,
) {
this.serviceContainer.register(ServiceIdentifier.create(ServiceIdentifier.NAMES.ApiGateway).getValue(), this)
constructor(private serviceContainer: ServiceContainerInterface) {
this.serviceContainer.register(this.getId(), this)
}
async handleRequest(request: never, response: never, endpointOrMethodIdentifier: string): Promise<unknown> {
const method = this.controllerContainer.get(endpointOrMethodIdentifier)
if (!method) {
throw new Error(`Method ${endpointOrMethodIdentifier} not found`)
}
return method(request, response)
async handleRequest(_request: never, _response: never, _endpointOrMethodIdentifier: string): Promise<unknown> {
throw new Error('Requests are handled via inversify-express at ApiGateway level')
}
async getContainer(): Promise<unknown> {

View file

@ -77,7 +77,7 @@ export class AuthMiddleware extends BaseMiddleware {
})
}
response.locals.userUuid = decodedToken.user.uuid
response.locals.user = decodedToken.user
response.locals.roles = decodedToken.roles
} catch (error) {
const errorMessage = (error as AxiosError).isAxiosError

View file

@ -118,7 +118,7 @@ export class SubscriptionTokenAuthMiddleware extends BaseMiddleware {
verify(authResponse.data.authToken, this.jwtSecret, { algorithms: ['HS256'] })
)
response.locals.userUuid = decodedToken.user.uuid
response.locals.user = decodedToken.user
response.locals.roles = decodedToken.roles
}
}

View file

@ -63,7 +63,7 @@ export class WebSocketAuthMiddleware extends BaseMiddleware {
response.locals.freeUser =
decodedToken.roles.length === 1 &&
decodedToken.roles.find((role) => role.name === RoleName.NAMES.CoreUser) !== undefined
response.locals.userUuid = decodedToken.user.uuid
response.locals.user = decodedToken.user
response.locals.roles = decodedToken.roles
} catch (error) {
const errorMessage = (error as AxiosError).isAxiosError

View file

@ -8,7 +8,7 @@ import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolv
@controller('/v1/items', TYPES.AuthMiddleware)
export class ItemsController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ServiceProxy) private serviceProxy: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
@ -16,7 +16,7 @@ export class ItemsController extends BaseHttpController {
@httpPost('/')
async sync(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
await this.serviceProxy.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('POST', 'items/sync'),
@ -26,7 +26,7 @@ export class ItemsController extends BaseHttpController {
@httpPost('/check-integrity')
async checkIntegrity(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
await this.serviceProxy.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('POST', 'items/check-integrity'),
@ -36,7 +36,7 @@ export class ItemsController extends BaseHttpController {
@httpGet('/:uuid')
async getItem(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
await this.serviceProxy.callSyncingServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('GET', 'items/:uuid', request.params.uuid),

View file

@ -227,7 +227,7 @@ export class UsersController extends BaseHttpController {
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'GET',
'users/:userUuid/subscription',
response.locals.userUuid,
response.locals.user.uuid,
),
)
}

View file

@ -262,7 +262,7 @@ export class HttpServiceProxy implements ServiceProxyInterface {
response.status(serviceResponse.status).send({
meta: {
auth: {
userUuid: response.locals.userUuid,
userUuid: response.locals.user?.uuid,
roles: response.locals.roles,
},
server: {

View file

@ -77,7 +77,12 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
throw new Error('Syncing service not found')
}
await service.handleRequest(request, response, endpointOrMethodIdentifier)
const serviceResponse = (await service.handleRequest(request, response, endpointOrMethodIdentifier)) as {
statusCode: number
json: Record<string, unknown>
}
void (response as Response).status(serviceResponse.statusCode).send(serviceResponse.json)
}
async callLegacySyncingServer(

View file

@ -51,6 +51,10 @@ export class EndpointResolver implements EndpointResolverInterface {
['[GET]:users/:userUuid/subscription', 'auth.users.getSubscription'],
['[GET]:offline/users/subscription', 'auth.users.getOfflineSubscriptionByToken'],
['[POST]:users/:userUuid/requests', 'auth.users.createRequest'],
// Syncing Server
['[POST]:items/sync', 'sync.items.sync'],
['[POST]:items/check-integrity', 'sync.items.check_integrity'],
['[GET]:items/:uuid', 'sync.items.get_item'],
])
resolveEndpointOrMethodIdentifier(method: string, endpoint: string, ...params: string[]): string {

View file

@ -517,7 +517,7 @@ export class ContainerConfigLoader {
.toConstantValue(env.get('USER_SERVER_CHANGE_EMAIL_URL', true))
container.bind(TYPES.Auth_NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
container.bind(TYPES.Auth_SYNCING_SERVER_URL).toConstantValue(env.get('SYNCING_SERVER_URL', true))
container.bind(TYPES.Auth_VERSION).toConstantValue(env.get('VERSION'))
container.bind(TYPES.Auth_VERSION).toConstantValue(env.get('VERSION', true) ?? 'development')
container.bind(TYPES.Auth_PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
container
.bind(TYPES.Auth_SESSION_TRACE_DAYS_TTL)

View file

@ -14,7 +14,7 @@ export class Service implements ServiceInterface {
private controllerContainer: ControllerContainerInterface,
private directCallDomainEventPublisher: DirectCallDomainEventPublisher,
) {
this.serviceContainer.register(ServiceIdentifier.create(ServiceIdentifier.NAMES.Auth).getValue(), this)
this.serviceContainer.register(this.getId(), this)
}
async handleRequest(request: never, response: never, endpointOrMethodIdentifier: string): Promise<unknown> {

View file

@ -4,6 +4,7 @@ import { ControllerContainer, ServiceContainer } from '@standardnotes/domain-cor
import { Service as ApiGatewayService, TYPES as ApiGatewayTYPES } from '@standardnotes/api-gateway'
import { DirectCallDomainEventPublisher } from '@standardnotes/domain-events-infra'
import { Service as AuthService } from '@standardnotes/auth-server'
import { Service as SyncingService } from '@standardnotes/syncing-server'
import { Container } from 'inversify'
import { InversifyExpressServer } from 'inversify-express-utils'
import helmet from 'helmet'
@ -20,12 +21,14 @@ const startServer = async (): Promise<void> => {
const serviceContainer = new ServiceContainer()
const directCallDomainEventPublisher = new DirectCallDomainEventPublisher()
const apiGatewayService = new ApiGatewayService(serviceContainer, controllerContainer)
const apiGatewayService = new ApiGatewayService(serviceContainer)
const authService = new AuthService(serviceContainer, controllerContainer, directCallDomainEventPublisher)
const syncingService = new SyncingService(serviceContainer, controllerContainer, directCallDomainEventPublisher)
const container = Container.merge(
(await apiGatewayService.getContainer()) as Container,
(await authService.getContainer()) as Container,
(await syncingService.getContainer()) as Container,
)
const env: Env = new Env()
@ -93,12 +96,7 @@ const startServer = async (): Promise<void> => {
logger.info(`Server started on port ${process.env.PORT}`)
}
Promise.resolve(startServer())
.then(() => {
// eslint-disable-next-line no-console
console.log('Server started')
})
.catch((error) => {
// eslint-disable-next-line no-console
console.log(`Could not start server: ${error.message}`)
})
Promise.resolve(startServer()).catch((error) => {
// eslint-disable-next-line no-console
console.log(`Could not start server: ${error.message}`)
})

View file

@ -22,6 +22,7 @@
"@standardnotes/auth-server": "workspace:^",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events-infra": "workspace:^",
"@standardnotes/syncing-server": "workspace:^",
"cors": "2.8.5",
"dotenv": "^16.0.1",
"express": "^4.18.2",

View file

@ -2,8 +2,8 @@ import 'reflect-metadata'
import 'newrelic'
import '../src/Controller/HealthCheckController'
import '../src/Controller/ItemsController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressHealthCheckController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressItemsController'
import helmet from 'helmet'
import * as cors from 'cors'
@ -13,9 +13,9 @@ import * as winston from 'winston'
import { InversifyExpressServer } from 'inversify-express-utils'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { ServerContainerConfigLoader } from '../src/Bootstrap/ServerContainerConfigLoader'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
const container = new ServerContainerConfigLoader()
const container = new ContainerConfigLoader()
void container.load().then((container) => {
const env: Env = new Env()
env.load()
@ -24,7 +24,7 @@ void container.load().then((container) => {
server.setConfig((app) => {
app.use((_request: Request, response: Response, next: NextFunction) => {
response.setHeader('X-SSJS-Version', container.get(TYPES.VERSION))
response.setHeader('X-SSJS-Version', container.get(TYPES.Sync_VERSION))
next()
})
/* eslint-disable */
@ -54,7 +54,7 @@ void container.load().then((container) => {
app.use(cors())
})
const logger: winston.Logger = container.get(TYPES.Logger)
const logger: winston.Logger = container.get(TYPES.Sync_Logger)
server.setErrorConfig((app) => {
app.use((error: Record<string, unknown>, _request: Request, response: Response, _next: NextFunction) => {

View file

@ -7,18 +7,20 @@ import { Logger } from 'winston'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { DomainEventSubscriberFactoryInterface } from '@standardnotes/domain-events'
import { WorkerContainerConfigLoader } from '../src/Bootstrap/WorkerContainerConfigLoader'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
const container = new WorkerContainerConfigLoader()
const container = new ContainerConfigLoader()
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Logger)
const logger: Logger = container.get(TYPES.Sync_Logger)
logger.info('Starting worker...')
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(
TYPES.Sync_DomainEventSubscriberFactory,
)
subscriberFactory.create().start()
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)

View file

@ -1,119 +0,0 @@
import * as winston from 'winston'
import { Container, interfaces } from 'inversify'
import { Env } from './Env'
import TYPES from './Types'
import { AppDataSource } from './DataSource'
import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
import { ItemRepositoryInterface } from '../Domain/Item/ItemRepositoryInterface'
import { TypeORMItemRepository } from '../Infra/TypeORM/TypeORMItemRepository'
import { Repository } from 'typeorm'
import { Item } from '../Domain/Item/Item'
import { ItemProjection } from '../Projection/ItemProjection'
import { ProjectorInterface } from '../Projection/ProjectorInterface'
import { ItemProjector } from '../Projection/ItemProjector'
import { SNSDomainEventPublisher } from '@standardnotes/domain-events-infra'
import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
import { Timer, TimerInterface } from '@standardnotes/time'
import { ItemTransferCalculatorInterface } from '../Domain/Item/ItemTransferCalculatorInterface'
import { ItemTransferCalculator } from '../Domain/Item/ItemTransferCalculator'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
export class CommonContainerConfigLoader {
async load(): Promise<Container> {
const env: Env = new Env()
env.load()
const container = new Container({
defaultScope: 'Singleton',
})
await AppDataSource.initialize()
container.bind<Env>(TYPES.Env).toConstantValue(env)
container.bind<winston.Logger>(TYPES.Logger).toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.Env)
const newrelicWinstonFormatter = newrelicFormatter(winston)
const winstonFormatters = [winston.format.splat(), winston.format.json()]
if (env.get('NEW_RELIC_ENABLED', true) === 'true') {
winstonFormatters.push(newrelicWinstonFormatter())
}
const logger = winston.createLogger({
level: env.get('LOG_LEVEL') || 'info',
format: winston.format.combine(...winstonFormatters),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL') || 'info' })],
})
return logger
})
container.bind<SNSClient>(TYPES.SNS).toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.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)
})
// Repositories
container.bind<ItemRepositoryInterface>(TYPES.ItemRepository).toDynamicValue((context: interfaces.Context) => {
return new TypeORMItemRepository(context.container.get(TYPES.ORMItemRepository))
})
// ORM
container.bind<Repository<Item>>(TYPES.ORMItemRepository).toDynamicValue(() => AppDataSource.getRepository(Item))
// Projectors
container
.bind<ProjectorInterface<Item, ItemProjection>>(TYPES.ItemProjector)
.toDynamicValue((context: interfaces.Context) => {
return new ItemProjector(context.container.get(TYPES.Timer))
})
// env vars
container.bind(TYPES.SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN'))
container.bind(TYPES.SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true))
container.bind<TimerInterface>(TYPES.Timer).toDynamicValue(() => new Timer())
container
.bind<SNSDomainEventPublisher>(TYPES.DomainEventPublisher)
.toDynamicValue((context: interfaces.Context) => {
return new SNSDomainEventPublisher(context.container.get(TYPES.SNS), context.container.get(TYPES.SNS_TOPIC_ARN))
})
container
.bind<DomainEventFactoryInterface>(TYPES.DomainEventFactory)
.toDynamicValue((context: interfaces.Context) => {
return new DomainEventFactory(context.container.get(TYPES.Timer))
})
container
.bind<ItemTransferCalculatorInterface>(TYPES.ItemTransferCalculator)
.toDynamicValue((context: interfaces.Context) => {
return new ItemTransferCalculator(
context.container.get(TYPES.ItemRepository),
context.container.get(TYPES.Logger),
)
})
return container
}
}

View file

@ -0,0 +1,516 @@
import * as winston from 'winston'
import { Container, interfaces } from 'inversify'
import { Env } from './Env'
import TYPES from './Types'
import { AppDataSource } from './DataSource'
import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
import { ItemRepositoryInterface } from '../Domain/Item/ItemRepositoryInterface'
import { TypeORMItemRepository } from '../Infra/TypeORM/TypeORMItemRepository'
import { Repository } from 'typeorm'
import { Item } from '../Domain/Item/Item'
import { ItemProjection } from '../Projection/ItemProjection'
import { ProjectorInterface } from '../Projection/ProjectorInterface'
import { ItemProjector } from '../Projection/ItemProjector'
import {
DirectCallDomainEventPublisher,
DirectCallEventMessageHandler,
SNSDomainEventPublisher,
SQSDomainEventSubscriberFactory,
SQSEventMessageHandler,
SQSNewRelicEventMessageHandler,
} from '@standardnotes/domain-events-infra'
import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
import { Timer, TimerInterface } from '@standardnotes/time'
import { ItemTransferCalculatorInterface } from '../Domain/Item/ItemTransferCalculatorInterface'
import { ItemTransferCalculator } from '../Domain/Item/ItemTransferCalculator'
import { ItemConflict } from '../Domain/Item/ItemConflict'
import { ItemFactory } from '../Domain/Item/ItemFactory'
import { ItemFactoryInterface } from '../Domain/Item/ItemFactoryInterface'
import { ItemService } from '../Domain/Item/ItemService'
import { ItemServiceInterface } from '../Domain/Item/ItemServiceInterface'
import { ContentFilter } from '../Domain/Item/SaveRule/ContentFilter'
import { ContentTypeFilter } from '../Domain/Item/SaveRule/ContentTypeFilter'
import { OwnershipFilter } from '../Domain/Item/SaveRule/OwnershipFilter'
import { TimeDifferenceFilter } from '../Domain/Item/SaveRule/TimeDifferenceFilter'
import { UuidFilter } from '../Domain/Item/SaveRule/UuidFilter'
import { ItemSaveValidator } from '../Domain/Item/SaveValidator/ItemSaveValidator'
import { ItemSaveValidatorInterface } from '../Domain/Item/SaveValidator/ItemSaveValidatorInterface'
import { SyncResponseFactory20161215 } from '../Domain/Item/SyncResponse/SyncResponseFactory20161215'
import { SyncResponseFactory20200115 } from '../Domain/Item/SyncResponse/SyncResponseFactory20200115'
import { SyncResponseFactoryResolver } from '../Domain/Item/SyncResponse/SyncResponseFactoryResolver'
import { SyncResponseFactoryResolverInterface } from '../Domain/Item/SyncResponse/SyncResponseFactoryResolverInterface'
import { CheckIntegrity } from '../Domain/UseCase/CheckIntegrity/CheckIntegrity'
import { GetItem } from '../Domain/UseCase/GetItem/GetItem'
import { SyncItems } from '../Domain/UseCase/SyncItems'
import { InversifyExpressAuthMiddleware } from '../Infra/InversifyExpressUtils/Middleware/InversifyExpressAuthMiddleware'
import { ItemConflictProjection } from '../Projection/ItemConflictProjection'
import { ItemConflictProjector } from '../Projection/ItemConflictProjector'
import { SavedItemProjection } from '../Projection/SavedItemProjection'
import { SavedItemProjector } from '../Projection/SavedItemProjector'
import { S3Client } from '@aws-sdk/client-s3'
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
import { ContentDecoder } from '@standardnotes/common'
import {
DomainEventMessageHandlerInterface,
DomainEventHandlerInterface,
DomainEventSubscriberFactoryInterface,
DomainEventPublisherInterface,
} from '@standardnotes/domain-events'
import axios, { AxiosInstance } from 'axios'
import { AuthHttpServiceInterface } from '../Domain/Auth/AuthHttpServiceInterface'
import { ExtensionsHttpService } from '../Domain/Extension/ExtensionsHttpService'
import { ExtensionsHttpServiceInterface } from '../Domain/Extension/ExtensionsHttpServiceInterface'
import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountDeletionRequestedEventHandler'
import { DuplicateItemSyncedEventHandler } from '../Domain/Handler/DuplicateItemSyncedEventHandler'
import { EmailBackupRequestedEventHandler } from '../Domain/Handler/EmailBackupRequestedEventHandler'
import { ItemRevisionCreationRequestedEventHandler } from '../Domain/Handler/ItemRevisionCreationRequestedEventHandler'
import { ItemBackupServiceInterface } from '../Domain/Item/ItemBackupServiceInterface'
import { FSItemBackupService } from '../Infra/FS/FSItemBackupService'
import { AuthHttpService } from '../Infra/HTTP/AuthHttpService'
import { S3ItemBackupService } from '../Infra/S3/S3ItemBackupService'
import { ControllerContainer, ControllerContainerInterface } from '@standardnotes/domain-core'
import { InversifyExpressItemsController } from '../Infra/InversifyExpressUtils/InversifyExpressItemsController'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
export class ContainerConfigLoader {
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
private readonly DEFAULT_MAX_ITEMS_LIMIT = 300
private readonly DEFAULT_FILE_UPLOAD_PATH = `${__dirname}/../../uploads`
async load(configuration?: {
controllerConatiner?: ControllerContainerInterface
directCallDomainEventPublisher?: DirectCallDomainEventPublisher
}): Promise<Container> {
const directCallDomainEventPublisher =
configuration?.directCallDomainEventPublisher ?? new DirectCallDomainEventPublisher()
const env: Env = new Env()
env.load()
const container = new Container({
defaultScope: 'Singleton',
})
await AppDataSource.initialize()
const isConfiguredForHomeServer = env.get('DB_TYPE') === 'sqlite'
container.bind<Env>(TYPES.Sync_Env).toConstantValue(env)
container.bind<winston.Logger>(TYPES.Sync_Logger).toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.Sync_Env)
const newrelicWinstonFormatter = newrelicFormatter(winston)
const winstonFormatters = [winston.format.splat(), winston.format.json()]
if (env.get('NEW_RELIC_ENABLED', true) === 'true') {
winstonFormatters.push(newrelicWinstonFormatter())
}
const logger = winston.createLogger({
level: env.get('LOG_LEVEL') || 'info',
format: winston.format.combine(...winstonFormatters),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL') || 'info' })],
})
return logger
})
if (isConfiguredForHomeServer) {
container
.bind<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher)
.toConstantValue(directCallDomainEventPublisher)
} else {
container.bind(TYPES.Sync_SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN'))
container.bind(TYPES.Sync_SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true))
container.bind(TYPES.Sync_SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL'))
container.bind(TYPES.Sync_S3_AWS_REGION).toConstantValue(env.get('S3_AWS_REGION', true))
container.bind(TYPES.Sync_S3_BACKUP_BUCKET_NAME).toConstantValue(env.get('S3_BACKUP_BUCKET_NAME', true))
container.bind(TYPES.Sync_EXTENSIONS_SERVER_URL).toConstantValue(env.get('EXTENSIONS_SERVER_URL', true))
container.bind<SNSClient>(TYPES.Sync_SNS).toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.Sync_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.Sync_DomainEventPublisher)
.toDynamicValue((context: interfaces.Context) => {
return new SNSDomainEventPublisher(
context.container.get(TYPES.Sync_SNS),
context.container.get(TYPES.Sync_SNS_TOPIC_ARN),
)
})
container.bind<SQSClient>(TYPES.Sync_SQS).toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.Sync_Env)
const sqsConfig: SQSClientConfig = {
region: env.get('SQS_AWS_REGION'),
}
if (env.get('SQS_ENDPOINT', true)) {
sqsConfig.endpoint = env.get('SQS_ENDPOINT', true)
}
if (env.get('SQS_ACCESS_KEY_ID', true) && env.get('SQS_SECRET_ACCESS_KEY', true)) {
sqsConfig.credentials = {
accessKeyId: env.get('SQS_ACCESS_KEY_ID', true),
secretAccessKey: env.get('SQS_SECRET_ACCESS_KEY', true),
}
}
return new SQSClient(sqsConfig)
})
container.bind<S3Client | undefined>(TYPES.Sync_S3).toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.Sync_Env)
let s3Client = undefined
if (env.get('S3_AWS_REGION', true)) {
s3Client = new S3Client({
apiVersion: 'latest',
region: env.get('S3_AWS_REGION', true),
})
}
return s3Client
})
}
// Repositories
container.bind<ItemRepositoryInterface>(TYPES.Sync_ItemRepository).toDynamicValue((context: interfaces.Context) => {
return new TypeORMItemRepository(context.container.get(TYPES.Sync_ORMItemRepository))
})
// ORM
container
.bind<Repository<Item>>(TYPES.Sync_ORMItemRepository)
.toDynamicValue(() => AppDataSource.getRepository(Item))
// Projectors
container
.bind<ProjectorInterface<Item, ItemProjection>>(TYPES.Sync_ItemProjector)
.toDynamicValue((context: interfaces.Context) => {
return new ItemProjector(context.container.get(TYPES.Sync_Timer))
})
container.bind<TimerInterface>(TYPES.Sync_Timer).toDynamicValue(() => new Timer())
container
.bind<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory)
.toDynamicValue((context: interfaces.Context) => {
return new DomainEventFactory(context.container.get(TYPES.Sync_Timer))
})
container
.bind<ItemTransferCalculatorInterface>(TYPES.Sync_ItemTransferCalculator)
.toDynamicValue((context: interfaces.Context) => {
return new ItemTransferCalculator(
context.container.get(TYPES.Sync_ItemRepository),
context.container.get(TYPES.Sync_Logger),
)
})
// Middleware
container
.bind<InversifyExpressAuthMiddleware>(TYPES.Sync_AuthMiddleware)
.toDynamicValue((context: interfaces.Context) => {
return new InversifyExpressAuthMiddleware(
context.container.get(TYPES.Sync_AUTH_JWT_SECRET),
context.container.get(TYPES.Sync_Logger),
)
})
// Projectors
container
.bind<ProjectorInterface<Item, SavedItemProjection>>(TYPES.Sync_SavedItemProjector)
.toDynamicValue((context: interfaces.Context) => {
return new SavedItemProjector(context.container.get(TYPES.Sync_Timer))
})
container
.bind<ProjectorInterface<ItemConflict, ItemConflictProjection>>(TYPES.Sync_ItemConflictProjector)
.toDynamicValue((context: interfaces.Context) => {
return new ItemConflictProjector(context.container.get(TYPES.Sync_ItemProjector))
})
// env vars
container.bind(TYPES.Sync_AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
container
.bind(TYPES.Sync_REVISIONS_FREQUENCY)
.toConstantValue(env.get('REVISIONS_FREQUENCY', true) ? +env.get('REVISIONS_FREQUENCY', true) : 300)
container.bind(TYPES.Sync_NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
container.bind(TYPES.Sync_VERSION).toConstantValue(env.get('VERSION', true) ?? 'development')
container
.bind(TYPES.Sync_CONTENT_SIZE_TRANSFER_LIMIT)
.toConstantValue(
env.get('CONTENT_SIZE_TRANSFER_LIMIT', true)
? +env.get('CONTENT_SIZE_TRANSFER_LIMIT', true)
: this.DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT,
)
container
.bind(TYPES.Sync_MAX_ITEMS_LIMIT)
.toConstantValue(
env.get('MAX_ITEMS_LIMIT', true) ? +env.get('MAX_ITEMS_LIMIT', true) : this.DEFAULT_MAX_ITEMS_LIMIT,
)
// use cases
container.bind<SyncItems>(TYPES.Sync_SyncItems).toDynamicValue((context: interfaces.Context) => {
return new SyncItems(context.container.get(TYPES.Sync_ItemService))
})
container.bind<CheckIntegrity>(TYPES.Sync_CheckIntegrity).toDynamicValue((context: interfaces.Context) => {
return new CheckIntegrity(context.container.get(TYPES.Sync_ItemRepository))
})
container.bind<GetItem>(TYPES.Sync_GetItem).toDynamicValue((context: interfaces.Context) => {
return new GetItem(context.container.get(TYPES.Sync_ItemRepository))
})
// Services
container.bind<ItemServiceInterface>(TYPES.Sync_ItemService).toDynamicValue((context: interfaces.Context) => {
return new ItemService(
context.container.get(TYPES.Sync_ItemSaveValidator),
context.container.get(TYPES.Sync_ItemFactory),
context.container.get(TYPES.Sync_ItemRepository),
context.container.get(TYPES.Sync_DomainEventPublisher),
context.container.get(TYPES.Sync_DomainEventFactory),
context.container.get(TYPES.Sync_REVISIONS_FREQUENCY),
context.container.get(TYPES.Sync_CONTENT_SIZE_TRANSFER_LIMIT),
context.container.get(TYPES.Sync_ItemTransferCalculator),
context.container.get(TYPES.Sync_Timer),
context.container.get(TYPES.Sync_ItemProjector),
context.container.get(TYPES.Sync_MAX_ITEMS_LIMIT),
context.container.get(TYPES.Sync_Logger),
)
})
container
.bind<SyncResponseFactory20161215>(TYPES.Sync_SyncResponseFactory20161215)
.toDynamicValue((context: interfaces.Context) => {
return new SyncResponseFactory20161215(context.container.get(TYPES.Sync_ItemProjector))
})
container
.bind<SyncResponseFactory20200115>(TYPES.Sync_SyncResponseFactory20200115)
.toDynamicValue((context: interfaces.Context) => {
return new SyncResponseFactory20200115(
context.container.get(TYPES.Sync_ItemProjector),
context.container.get(TYPES.Sync_ItemConflictProjector),
context.container.get(TYPES.Sync_SavedItemProjector),
)
})
container
.bind<SyncResponseFactoryResolverInterface>(TYPES.Sync_SyncResponseFactoryResolver)
.toDynamicValue((context: interfaces.Context) => {
return new SyncResponseFactoryResolver(
context.container.get(TYPES.Sync_SyncResponseFactory20161215),
context.container.get(TYPES.Sync_SyncResponseFactory20200115),
)
})
container.bind<ItemFactoryInterface>(TYPES.Sync_ItemFactory).toDynamicValue((context: interfaces.Context) => {
return new ItemFactory(context.container.get(TYPES.Sync_Timer), context.container.get(TYPES.Sync_ItemProjector))
})
container.bind<OwnershipFilter>(TYPES.Sync_OwnershipFilter).toDynamicValue(() => new OwnershipFilter())
container
.bind<TimeDifferenceFilter>(TYPES.Sync_TimeDifferenceFilter)
.toDynamicValue(
(context: interfaces.Context) => new TimeDifferenceFilter(context.container.get(TYPES.Sync_Timer)),
)
container.bind<UuidFilter>(TYPES.Sync_UuidFilter).toDynamicValue(() => new UuidFilter())
container.bind<ContentTypeFilter>(TYPES.Sync_ContentTypeFilter).toDynamicValue(() => new ContentTypeFilter())
container.bind<ContentFilter>(TYPES.Sync_ContentFilter).toDynamicValue(() => new ContentFilter())
container
.bind<ItemSaveValidatorInterface>(TYPES.Sync_ItemSaveValidator)
.toDynamicValue((context: interfaces.Context) => {
return new ItemSaveValidator([
context.container.get(TYPES.Sync_OwnershipFilter),
context.container.get(TYPES.Sync_TimeDifferenceFilter),
context.container.get(TYPES.Sync_UuidFilter),
context.container.get(TYPES.Sync_ContentTypeFilter),
context.container.get(TYPES.Sync_ContentFilter),
])
})
// env vars
container
.bind(TYPES.Sync_EMAIL_ATTACHMENT_MAX_BYTE_SIZE)
.toConstantValue(
env.get('EMAIL_ATTACHMENT_MAX_BYTE_SIZE', true) ? +env.get('EMAIL_ATTACHMENT_MAX_BYTE_SIZE', true) : 10485760,
)
container.bind(TYPES.Sync_NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
container
.bind(TYPES.Sync_FILE_UPLOAD_PATH)
.toConstantValue(
env.get('FILE_UPLOAD_PATH', true) ? env.get('FILE_UPLOAD_PATH', true) : this.DEFAULT_FILE_UPLOAD_PATH,
)
// Handlers
container
.bind<DuplicateItemSyncedEventHandler>(TYPES.Sync_DuplicateItemSyncedEventHandler)
.toDynamicValue((context: interfaces.Context) => {
return new DuplicateItemSyncedEventHandler(
context.container.get(TYPES.Sync_ItemRepository),
context.container.get(TYPES.Sync_DomainEventFactory),
context.container.get(TYPES.Sync_DomainEventPublisher),
context.container.get(TYPES.Sync_Logger),
)
})
container
.bind<AccountDeletionRequestedEventHandler>(TYPES.Sync_AccountDeletionRequestedEventHandler)
.toDynamicValue((context: interfaces.Context) => {
return new AccountDeletionRequestedEventHandler(
context.container.get(TYPES.Sync_ItemRepository),
context.container.get(TYPES.Sync_Logger),
)
})
container
.bind<ItemRevisionCreationRequestedEventHandler>(TYPES.Sync_ItemRevisionCreationRequestedEventHandler)
.toDynamicValue((context: interfaces.Context) => {
return new ItemRevisionCreationRequestedEventHandler(
context.container.get(TYPES.Sync_ItemRepository),
context.container.get(TYPES.Sync_ItemBackupService),
context.container.get(TYPES.Sync_DomainEventFactory),
context.container.get(TYPES.Sync_DomainEventPublisher),
)
})
// Services
container.bind<ContentDecoder>(TYPES.Sync_ContentDecoder).toDynamicValue(() => new ContentDecoder())
container.bind<AxiosInstance>(TYPES.Sync_HTTPClient).toDynamicValue(() => axios.create())
container
.bind<ExtensionsHttpServiceInterface>(TYPES.Sync_ExtensionsHttpService)
.toDynamicValue((context: interfaces.Context) => {
return new ExtensionsHttpService(
context.container.get(TYPES.Sync_HTTPClient),
context.container.get(TYPES.Sync_ItemRepository),
context.container.get(TYPES.Sync_ContentDecoder),
context.container.get(TYPES.Sync_DomainEventPublisher),
context.container.get(TYPES.Sync_DomainEventFactory),
context.container.get(TYPES.Sync_Logger),
)
})
container
.bind<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService)
.toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.Sync_Env)
if (env.get('S3_AWS_REGION', true)) {
return new S3ItemBackupService(
context.container.get(TYPES.Sync_S3_BACKUP_BUCKET_NAME),
context.container.get(TYPES.Sync_ItemProjector),
context.container.get(TYPES.Sync_Logger),
context.container.get(TYPES.Sync_S3),
)
} else {
return new FSItemBackupService(
context.container.get(TYPES.Sync_FILE_UPLOAD_PATH),
context.container.get(TYPES.Sync_ItemProjector),
context.container.get(TYPES.Sync_Logger),
)
}
})
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['DUPLICATE_ITEM_SYNCED', container.get(TYPES.Sync_DuplicateItemSyncedEventHandler)],
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Sync_AccountDeletionRequestedEventHandler)],
['ITEM_REVISION_CREATION_REQUESTED', container.get(TYPES.Sync_ItemRevisionCreationRequestedEventHandler)],
])
if (!isConfiguredForHomeServer) {
container.bind(TYPES.Sync_AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))
container
.bind<AuthHttpServiceInterface>(TYPES.Sync_AuthHttpService)
.toDynamicValue((context: interfaces.Context) => {
return new AuthHttpService(
context.container.get(TYPES.Sync_HTTPClient),
context.container.get(TYPES.Sync_AUTH_SERVER_URL),
)
})
container
.bind<EmailBackupRequestedEventHandler>(TYPES.Sync_EmailBackupRequestedEventHandler)
.toDynamicValue((context: interfaces.Context) => {
return new EmailBackupRequestedEventHandler(
context.container.get(TYPES.Sync_ItemRepository),
context.container.get(TYPES.Sync_AuthHttpService),
context.container.get(TYPES.Sync_ItemBackupService),
context.container.get(TYPES.Sync_DomainEventPublisher),
context.container.get(TYPES.Sync_DomainEventFactory),
context.container.get(TYPES.Sync_EMAIL_ATTACHMENT_MAX_BYTE_SIZE),
context.container.get(TYPES.Sync_ItemTransferCalculator),
context.container.get(TYPES.Sync_S3_BACKUP_BUCKET_NAME),
context.container.get(TYPES.Sync_Logger),
)
})
eventHandlers.set('EMAIL_BACKUP_REQUESTED', container.get(TYPES.Sync_EmailBackupRequestedEventHandler))
}
if (isConfiguredForHomeServer) {
const directCallEventMessageHandler = new DirectCallEventMessageHandler(
eventHandlers,
container.get(TYPES.Sync_Logger),
)
directCallDomainEventPublisher.register(directCallEventMessageHandler)
container
.bind<DomainEventMessageHandlerInterface>(TYPES.Sync_DomainEventMessageHandler)
.toConstantValue(directCallEventMessageHandler)
} else {
container
.bind<DomainEventMessageHandlerInterface>(TYPES.Sync_DomainEventMessageHandler)
.toConstantValue(
env.get('NEW_RELIC_ENABLED', true) === 'true'
? new SQSNewRelicEventMessageHandler(eventHandlers, container.get(TYPES.Sync_Logger))
: new SQSEventMessageHandler(eventHandlers, container.get(TYPES.Sync_Logger)),
)
}
container
.bind<DomainEventSubscriberFactoryInterface>(TYPES.Sync_DomainEventSubscriberFactory)
.toDynamicValue((context: interfaces.Context) => {
return new SQSDomainEventSubscriberFactory(
context.container.get(TYPES.Sync_SQS),
context.container.get(TYPES.Sync_SQS_QUEUE_URL),
context.container.get(TYPES.Sync_DomainEventMessageHandler),
)
})
container
.bind<ControllerContainerInterface>(TYPES.Sync_ControllerContainer)
.toConstantValue(configuration?.controllerConatiner ?? new ControllerContainer())
if (isConfiguredForHomeServer) {
container
.bind<InversifyExpressItemsController>(TYPES.Sync_InversifyExpressItemsController)
.toConstantValue(
new InversifyExpressItemsController(
container.get(TYPES.Sync_SyncItems),
container.get(TYPES.Sync_CheckIntegrity),
container.get(TYPES.Sync_GetItem),
container.get(TYPES.Sync_ItemProjector),
container.get(TYPES.Sync_SyncResponseFactoryResolver),
container.get(TYPES.Sync_ControllerContainer),
),
)
}
return container
}
}

View file

@ -13,55 +13,62 @@ const maxQueryExecutionTime = env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
? +env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
: 45_000
const inReplicaMode = env.get('DB_REPLICA_HOST', true) ? true : false
const commonDataSourceOptions = {
maxQueryExecutionTime,
entities: [Item],
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
migrationsRun: true,
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL', true) ?? 'info',
}
const replicationConfig = {
master: {
host: env.get('DB_HOST'),
port: parseInt(env.get('DB_PORT')),
username: env.get('DB_USERNAME'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
slaves: [
{
host: env.get('DB_REPLICA_HOST', true),
let dataSource: DataSource
if (isConfiguredForMySQL) {
const inReplicaMode = env.get('DB_REPLICA_HOST', true) ? true : false
const replicationConfig = {
master: {
host: env.get('DB_HOST'),
port: parseInt(env.get('DB_PORT')),
username: env.get('DB_USERNAME'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
],
removeNodeErrorCount: 10,
restoreNodeTimeout: 5,
slaves: [
{
host: env.get('DB_REPLICA_HOST', true),
port: parseInt(env.get('DB_PORT')),
username: env.get('DB_USERNAME'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
],
removeNodeErrorCount: 10,
restoreNodeTimeout: 5,
}
const mySQLDataSourceOptions: MysqlConnectionOptions = {
...commonDataSourceOptions,
type: 'mysql',
charset: 'utf8mb4',
supportBigNumbers: true,
bigNumberStrings: false,
replication: inReplicaMode ? replicationConfig : undefined,
host: inReplicaMode ? undefined : env.get('DB_HOST'),
port: inReplicaMode ? undefined : parseInt(env.get('DB_PORT')),
username: inReplicaMode ? undefined : env.get('DB_USERNAME'),
password: inReplicaMode ? undefined : env.get('DB_PASSWORD'),
database: inReplicaMode ? undefined : env.get('DB_DATABASE'),
}
dataSource = new DataSource(mySQLDataSourceOptions)
} else {
const sqliteDataSourceOptions: SqliteConnectionOptions = {
...commonDataSourceOptions,
type: 'sqlite',
database: `data/${env.get('DB_DATABASE')}.sqlite`,
}
dataSource = new DataSource(sqliteDataSourceOptions)
}
const commonDataSourceOptions = {
maxQueryExecutionTime,
entities: [Item],
migrations: [`dist/migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
migrationsRun: true,
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
}
const mySQLDataSourceOptions: MysqlConnectionOptions = {
...commonDataSourceOptions,
type: 'mysql',
charset: 'utf8mb4',
supportBigNumbers: true,
bigNumberStrings: false,
replication: inReplicaMode ? replicationConfig : undefined,
host: inReplicaMode ? undefined : env.get('DB_HOST'),
port: inReplicaMode ? undefined : parseInt(env.get('DB_PORT')),
username: inReplicaMode ? undefined : env.get('DB_USERNAME'),
password: inReplicaMode ? undefined : env.get('DB_PASSWORD'),
database: inReplicaMode ? undefined : env.get('DB_DATABASE'),
}
const sqliteDataSourceOptions: SqliteConnectionOptions = {
...commonDataSourceOptions,
type: 'sqlite',
database: `data/${env.get('DB_DATABASE')}.sqlite`,
}
export const AppDataSource = new DataSource(isConfiguredForMySQL ? mySQLDataSourceOptions : sqliteDataSourceOptions)
export const AppDataSource = dataSource

View file

@ -1,154 +0,0 @@
import { Container, interfaces } from 'inversify'
import { Env } from './Env'
import TYPES from './Types'
import { AuthMiddleware } from '../Controller/AuthMiddleware'
import { Item } from '../Domain/Item/Item'
import { SyncResponseFactory20161215 } from '../Domain/Item/SyncResponse/SyncResponseFactory20161215'
import { SyncResponseFactory20200115 } from '../Domain/Item/SyncResponse/SyncResponseFactory20200115'
import { SyncResponseFactoryResolverInterface } from '../Domain/Item/SyncResponse/SyncResponseFactoryResolverInterface'
import { SyncResponseFactoryResolver } from '../Domain/Item/SyncResponse/SyncResponseFactoryResolver'
import { ItemServiceInterface } from '../Domain/Item/ItemServiceInterface'
import { ItemService } from '../Domain/Item/ItemService'
import { SyncItems } from '../Domain/UseCase/SyncItems'
import { ItemConflictProjector } from '../Projection/ItemConflictProjector'
import { ItemSaveValidatorInterface } from '../Domain/Item/SaveValidator/ItemSaveValidatorInterface'
import { ItemSaveValidator } from '../Domain/Item/SaveValidator/ItemSaveValidator'
import { OwnershipFilter } from '../Domain/Item/SaveRule/OwnershipFilter'
import { TimeDifferenceFilter } from '../Domain/Item/SaveRule/TimeDifferenceFilter'
import { ItemFactoryInterface } from '../Domain/Item/ItemFactoryInterface'
import { ItemFactory } from '../Domain/Item/ItemFactory'
import { UuidFilter } from '../Domain/Item/SaveRule/UuidFilter'
import { ContentTypeFilter } from '../Domain/Item/SaveRule/ContentTypeFilter'
import { ContentFilter } from '../Domain/Item/SaveRule/ContentFilter'
import { CheckIntegrity } from '../Domain/UseCase/CheckIntegrity/CheckIntegrity'
import { GetItem } from '../Domain/UseCase/GetItem/GetItem'
import { ProjectorInterface } from '../Projection/ProjectorInterface'
import { SavedItemProjection } from '../Projection/SavedItemProjection'
import { SavedItemProjector } from '../Projection/SavedItemProjector'
import { ItemConflict } from '../Domain/Item/ItemConflict'
import { ItemConflictProjection } from '../Projection/ItemConflictProjection'
import { CommonContainerConfigLoader } from './CommonContainerConfigLoader'
export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
private readonly DEFAULT_MAX_ITEMS_LIMIT = 300
override async load(): Promise<Container> {
const container = await super.load()
const env: Env = container.get(TYPES.Env)
// Middleware
container.bind<AuthMiddleware>(TYPES.AuthMiddleware).toDynamicValue((context: interfaces.Context) => {
return new AuthMiddleware(context.container.get(TYPES.AUTH_JWT_SECRET), context.container.get(TYPES.Logger))
})
// Projectors
container
.bind<ProjectorInterface<Item, SavedItemProjection>>(TYPES.SavedItemProjector)
.toDynamicValue((context: interfaces.Context) => {
return new SavedItemProjector(context.container.get(TYPES.Timer))
})
container
.bind<ProjectorInterface<ItemConflict, ItemConflictProjection>>(TYPES.ItemConflictProjector)
.toDynamicValue((context: interfaces.Context) => {
return new ItemConflictProjector(context.container.get(TYPES.ItemProjector))
})
// env vars
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
container.bind(TYPES.REVISIONS_FREQUENCY).toConstantValue(env.get('REVISIONS_FREQUENCY'))
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION'))
container
.bind(TYPES.CONTENT_SIZE_TRANSFER_LIMIT)
.toConstantValue(
env.get('CONTENT_SIZE_TRANSFER_LIMIT', true)
? +env.get('CONTENT_SIZE_TRANSFER_LIMIT', true)
: this.DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT,
)
container
.bind(TYPES.MAX_ITEMS_LIMIT)
.toConstantValue(
env.get('MAX_ITEMS_LIMIT', true) ? +env.get('MAX_ITEMS_LIMIT', true) : this.DEFAULT_MAX_ITEMS_LIMIT,
)
// use cases
container.bind<SyncItems>(TYPES.SyncItems).toDynamicValue((context: interfaces.Context) => {
return new SyncItems(context.container.get(TYPES.ItemService))
})
container.bind<CheckIntegrity>(TYPES.CheckIntegrity).toDynamicValue((context: interfaces.Context) => {
return new CheckIntegrity(context.container.get(TYPES.ItemRepository))
})
container.bind<GetItem>(TYPES.GetItem).toDynamicValue((context: interfaces.Context) => {
return new GetItem(context.container.get(TYPES.ItemRepository))
})
// Services
container.bind<ItemServiceInterface>(TYPES.ItemService).toDynamicValue((context: interfaces.Context) => {
return new ItemService(
context.container.get(TYPES.ItemSaveValidator),
context.container.get(TYPES.ItemFactory),
context.container.get(TYPES.ItemRepository),
context.container.get(TYPES.DomainEventPublisher),
context.container.get(TYPES.DomainEventFactory),
context.container.get(TYPES.REVISIONS_FREQUENCY),
context.container.get(TYPES.CONTENT_SIZE_TRANSFER_LIMIT),
context.container.get(TYPES.ItemTransferCalculator),
context.container.get(TYPES.Timer),
context.container.get(TYPES.ItemProjector),
context.container.get(TYPES.MAX_ITEMS_LIMIT),
context.container.get(TYPES.Logger),
)
})
container
.bind<SyncResponseFactory20161215>(TYPES.SyncResponseFactory20161215)
.toDynamicValue((context: interfaces.Context) => {
return new SyncResponseFactory20161215(context.container.get(TYPES.ItemProjector))
})
container
.bind<SyncResponseFactory20200115>(TYPES.SyncResponseFactory20200115)
.toDynamicValue((context: interfaces.Context) => {
return new SyncResponseFactory20200115(
context.container.get(TYPES.ItemProjector),
context.container.get(TYPES.ItemConflictProjector),
context.container.get(TYPES.SavedItemProjector),
)
})
container
.bind<SyncResponseFactoryResolverInterface>(TYPES.SyncResponseFactoryResolver)
.toDynamicValue((context: interfaces.Context) => {
return new SyncResponseFactoryResolver(
context.container.get(TYPES.SyncResponseFactory20161215),
context.container.get(TYPES.SyncResponseFactory20200115),
)
})
container.bind<ItemFactoryInterface>(TYPES.ItemFactory).toDynamicValue((context: interfaces.Context) => {
return new ItemFactory(context.container.get(TYPES.Timer), context.container.get(TYPES.ItemProjector))
})
container.bind<OwnershipFilter>(TYPES.OwnershipFilter).toDynamicValue(() => new OwnershipFilter())
container
.bind<TimeDifferenceFilter>(TYPES.TimeDifferenceFilter)
.toDynamicValue((context: interfaces.Context) => new TimeDifferenceFilter(context.container.get(TYPES.Timer)))
container.bind<UuidFilter>(TYPES.UuidFilter).toDynamicValue(() => new UuidFilter())
container.bind<ContentTypeFilter>(TYPES.ContentTypeFilter).toDynamicValue(() => new ContentTypeFilter())
container.bind<ContentFilter>(TYPES.ContentFilter).toDynamicValue(() => new ContentFilter())
container
.bind<ItemSaveValidatorInterface>(TYPES.ItemSaveValidator)
.toDynamicValue((context: interfaces.Context) => {
return new ItemSaveValidator([
context.container.get(TYPES.OwnershipFilter),
context.container.get(TYPES.TimeDifferenceFilter),
context.container.get(TYPES.UuidFilter),
context.container.get(TYPES.ContentTypeFilter),
context.container.get(TYPES.ContentFilter),
])
})
return container
}
}

View file

@ -0,0 +1,42 @@
import {
ControllerContainerInterface,
ServiceContainerInterface,
ServiceIdentifier,
ServiceInterface,
} from '@standardnotes/domain-core'
import { ContainerConfigLoader } from './Container'
import { DirectCallDomainEventPublisher } from '@standardnotes/domain-events-infra'
export class Service implements ServiceInterface {
constructor(
private serviceContainer: ServiceContainerInterface,
private controllerContainer: ControllerContainerInterface,
private directCallDomainEventPublisher: DirectCallDomainEventPublisher,
) {
this.serviceContainer.register(this.getId(), this)
}
async handleRequest(request: never, response: never, endpointOrMethodIdentifier: string): Promise<unknown> {
const method = this.controllerContainer.get(endpointOrMethodIdentifier)
if (!method) {
throw new Error(`Method ${endpointOrMethodIdentifier} not found`)
}
return method(request, response)
}
async getContainer(): Promise<unknown> {
const config = new ContainerConfigLoader()
return config.load({
controllerConatiner: this.controllerContainer,
directCallDomainEventPublisher: this.directCallDomainEventPublisher,
})
}
getId(): ServiceIdentifier {
return ServiceIdentifier.create(ServiceIdentifier.NAMES.SyncingServer).getValue()
}
}

View file

@ -1,71 +1,73 @@
const TYPES = {
DBConnection: Symbol.for('DBConnection'),
Logger: Symbol.for('Logger'),
Redis: Symbol.for('Redis'),
SNS: Symbol.for('SNS'),
SQS: Symbol.for('SQS'),
S3: Symbol.for('S3'),
Env: Symbol.for('Env'),
Sync_DBConnection: Symbol.for('Sync_DBConnection'),
Sync_Logger: Symbol.for('Sync_Logger'),
Sync_Redis: Symbol.for('Sync_Redis'),
Sync_SNS: Symbol.for('Sync_SNS'),
Sync_SQS: Symbol.for('Sync_SQS'),
Sync_S3: Symbol.for('Sync_S3'),
Sync_Env: Symbol.for('Sync_Env'),
// Repositories
ItemRepository: Symbol.for('ItemRepository'),
Sync_ItemRepository: Symbol.for('Sync_ItemRepository'),
// ORM
ORMItemRepository: Symbol.for('ORMItemRepository'),
Sync_ORMItemRepository: Symbol.for('Sync_ORMItemRepository'),
// Middleware
AuthMiddleware: Symbol.for('AuthMiddleware'),
Sync_AuthMiddleware: Symbol.for('Sync_AuthMiddleware'),
// Projectors
ItemProjector: Symbol.for('ItemProjector'),
SavedItemProjector: Symbol.for('SavedItemProjector'),
ItemConflictProjector: Symbol.for('ItemConflictProjector'),
Sync_ItemProjector: Symbol.for('Sync_ItemProjector'),
Sync_SavedItemProjector: Symbol.for('Sync_SavedItemProjector'),
Sync_ItemConflictProjector: Symbol.for('Sync_ItemConflictProjector'),
// env vars
REDIS_URL: Symbol.for('REDIS_URL'),
SNS_TOPIC_ARN: Symbol.for('SNS_TOPIC_ARN'),
SNS_AWS_REGION: Symbol.for('SNS_AWS_REGION'),
SQS_QUEUE_URL: Symbol.for('SQS_QUEUE_URL'),
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
EXTENSIONS_SERVER_URL: Symbol.for('EXTENSIONS_SERVER_URL'),
AUTH_SERVER_URL: Symbol.for('AUTH_SERVER_URL'),
S3_AWS_REGION: Symbol.for('S3_AWS_REGION'),
S3_BACKUP_BUCKET_NAME: Symbol.for('S3_BACKUP_BUCKET_NAME'),
EMAIL_ATTACHMENT_MAX_BYTE_SIZE: Symbol.for('EMAIL_ATTACHMENT_MAX_BYTE_SIZE'),
REVISIONS_FREQUENCY: Symbol.for('REVISIONS_FREQUENCY'),
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
VERSION: Symbol.for('VERSION'),
CONTENT_SIZE_TRANSFER_LIMIT: Symbol.for('CONTENT_SIZE_TRANSFER_LIMIT'),
MAX_ITEMS_LIMIT: Symbol.for('MAX_ITEMS_LIMIT'),
FILE_UPLOAD_PATH: Symbol.for('FILE_UPLOAD_PATH'),
Sync_REDIS_URL: Symbol.for('Sync_REDIS_URL'),
Sync_SNS_TOPIC_ARN: Symbol.for('Sync_SNS_TOPIC_ARN'),
Sync_SNS_AWS_REGION: Symbol.for('Sync_SNS_AWS_REGION'),
Sync_SQS_QUEUE_URL: Symbol.for('Sync_SQS_QUEUE_URL'),
Sync_SQS_AWS_REGION: Symbol.for('Sync_SQS_AWS_REGION'),
Sync_AUTH_JWT_SECRET: Symbol.for('Sync_AUTH_JWT_SECRET'),
Sync_EXTENSIONS_SERVER_URL: Symbol.for('Sync_EXTENSIONS_SERVER_URL'),
Sync_AUTH_SERVER_URL: Symbol.for('Sync_AUTH_SERVER_URL'),
Sync_S3_AWS_REGION: Symbol.for('Sync_S3_AWS_REGION'),
Sync_S3_BACKUP_BUCKET_NAME: Symbol.for('Sync_S3_BACKUP_BUCKET_NAME'),
Sync_EMAIL_ATTACHMENT_MAX_BYTE_SIZE: Symbol.for('Sync_EMAIL_ATTACHMENT_MAX_BYTE_SIZE'),
Sync_REVISIONS_FREQUENCY: Symbol.for('Sync_REVISIONS_FREQUENCY'),
Sync_NEW_RELIC_ENABLED: Symbol.for('Sync_NEW_RELIC_ENABLED'),
Sync_VERSION: Symbol.for('Sync_VERSION'),
Sync_CONTENT_SIZE_TRANSFER_LIMIT: Symbol.for('Sync_CONTENT_SIZE_TRANSFER_LIMIT'),
Sync_MAX_ITEMS_LIMIT: Symbol.for('Sync_MAX_ITEMS_LIMIT'),
Sync_FILE_UPLOAD_PATH: Symbol.for('Sync_FILE_UPLOAD_PATH'),
// use cases
SyncItems: Symbol.for('SyncItems'),
CheckIntegrity: Symbol.for('CheckIntegrity'),
GetItem: Symbol.for('GetItem'),
Sync_SyncItems: Symbol.for('Sync_SyncItems'),
Sync_CheckIntegrity: Symbol.for('Sync_CheckIntegrity'),
Sync_GetItem: Symbol.for('Sync_GetItem'),
// Handlers
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
DuplicateItemSyncedEventHandler: Symbol.for('DuplicateItemSyncedEventHandler'),
EmailBackupRequestedEventHandler: Symbol.for('EmailBackupRequestedEventHandler'),
ItemRevisionCreationRequestedEventHandler: Symbol.for('ItemRevisionCreationRequestedEventHandler'),
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
Sync_EmailBackupRequestedEventHandler: Symbol.for('Sync_EmailBackupRequestedEventHandler'),
Sync_ItemRevisionCreationRequestedEventHandler: Symbol.for('Sync_ItemRevisionCreationRequestedEventHandler'),
// Services
ContentDecoder: Symbol.for('ContentDecoder'),
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
DomainEventFactory: Symbol.for('DomainEventFactory'),
DomainEventMessageHandler: Symbol.for('DomainEventMessageHandler'),
HTTPClient: Symbol.for('HTTPClient'),
ItemService: Symbol.for('ItemService'),
Timer: Symbol.for('Timer'),
SyncResponseFactory20161215: Symbol.for('SyncResponseFactory20161215'),
SyncResponseFactory20200115: Symbol.for('SyncResponseFactory20200115'),
SyncResponseFactoryResolver: Symbol.for('SyncResponseFactoryResolver'),
AuthHttpService: Symbol.for('AuthHttpService'),
ExtensionsHttpService: Symbol.for('ExtensionsHttpService'),
ItemBackupService: Symbol.for('ItemBackupService'),
ItemSaveValidator: Symbol.for('ItemSaveValidator'),
OwnershipFilter: Symbol.for('OwnershipFilter'),
TimeDifferenceFilter: Symbol.for('TimeDifferenceFilter'),
UuidFilter: Symbol.for('UuidFilter'),
ContentTypeFilter: Symbol.for('ContentTypeFilter'),
ContentFilter: Symbol.for('ContentFilter'),
ItemFactory: Symbol.for('ItemFactory'),
ItemTransferCalculator: Symbol.for('ItemTransferCalculator'),
Sync_ContentDecoder: Symbol.for('Sync_ContentDecoder'),
Sync_DomainEventPublisher: Symbol.for('Sync_DomainEventPublisher'),
Sync_DomainEventSubscriberFactory: Symbol.for('Sync_DomainEventSubscriberFactory'),
Sync_DomainEventFactory: Symbol.for('Sync_DomainEventFactory'),
Sync_DomainEventMessageHandler: Symbol.for('Sync_DomainEventMessageHandler'),
Sync_HTTPClient: Symbol.for('Sync_HTTPClient'),
Sync_ItemService: Symbol.for('Sync_ItemService'),
Sync_Timer: Symbol.for('Sync_Timer'),
Sync_SyncResponseFactory20161215: Symbol.for('Sync_SyncResponseFactory20161215'),
Sync_SyncResponseFactory20200115: Symbol.for('Sync_SyncResponseFactory20200115'),
Sync_SyncResponseFactoryResolver: Symbol.for('Sync_SyncResponseFactoryResolver'),
Sync_AuthHttpService: Symbol.for('Sync_AuthHttpService'),
Sync_ExtensionsHttpService: Symbol.for('Sync_ExtensionsHttpService'),
Sync_ItemBackupService: Symbol.for('Sync_ItemBackupService'),
Sync_ItemSaveValidator: Symbol.for('Sync_ItemSaveValidator'),
Sync_OwnershipFilter: Symbol.for('Sync_OwnershipFilter'),
Sync_TimeDifferenceFilter: Symbol.for('Sync_TimeDifferenceFilter'),
Sync_UuidFilter: Symbol.for('Sync_UuidFilter'),
Sync_ContentTypeFilter: Symbol.for('Sync_ContentTypeFilter'),
Sync_ContentFilter: Symbol.for('Sync_ContentFilter'),
Sync_ItemFactory: Symbol.for('Sync_ItemFactory'),
Sync_ItemTransferCalculator: Symbol.for('Sync_ItemTransferCalculator'),
Sync_ControllerContainer: Symbol.for('Sync_ControllerContainer'),
Sync_InversifyExpressItemsController: Symbol.for('Sync_InversifyExpressItemsController'),
}
export default TYPES

View file

@ -1,207 +0,0 @@
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
import { S3Client } from '@aws-sdk/client-s3'
import { Container, interfaces } from 'inversify'
import {
DomainEventHandlerInterface,
DomainEventMessageHandlerInterface,
DomainEventSubscriberFactoryInterface,
} from '@standardnotes/domain-events'
import { Env } from './Env'
import TYPES from './Types'
import { ContentDecoder } from '../Domain/Item/ContentDecoder'
import { AuthHttpServiceInterface } from '../Domain/Auth/AuthHttpServiceInterface'
import { AuthHttpService } from '../Infra/HTTP/AuthHttpService'
import { ExtensionsHttpServiceInterface } from '../Domain/Extension/ExtensionsHttpServiceInterface'
import { ExtensionsHttpService } from '../Domain/Extension/ExtensionsHttpService'
import { ItemBackupServiceInterface } from '../Domain/Item/ItemBackupServiceInterface'
import { S3ItemBackupService } from '../Infra/S3/S3ItemBackupService'
import { DuplicateItemSyncedEventHandler } from '../Domain/Handler/DuplicateItemSyncedEventHandler'
import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountDeletionRequestedEventHandler'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const axios = require('axios')
import { AxiosInstance } from 'axios'
import {
SQSDomainEventSubscriberFactory,
SQSEventMessageHandler,
SQSNewRelicEventMessageHandler,
} from '@standardnotes/domain-events-infra'
import { EmailBackupRequestedEventHandler } from '../Domain/Handler/EmailBackupRequestedEventHandler'
import { ItemRevisionCreationRequestedEventHandler } from '../Domain/Handler/ItemRevisionCreationRequestedEventHandler'
import { FSItemBackupService } from '../Infra/FS/FSItemBackupService'
import { CommonContainerConfigLoader } from './CommonContainerConfigLoader'
export class WorkerContainerConfigLoader extends CommonContainerConfigLoader {
private readonly DEFAULT_FILE_UPLOAD_PATH = `${__dirname}/../../uploads`
override async load(): Promise<Container> {
const container = await super.load()
const env: Env = container.get(TYPES.Env)
container.bind<SQSClient>(TYPES.SQS).toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.Env)
const sqsConfig: SQSClientConfig = {
region: env.get('SQS_AWS_REGION'),
}
if (env.get('SQS_ENDPOINT', true)) {
sqsConfig.endpoint = env.get('SQS_ENDPOINT', true)
}
if (env.get('SQS_ACCESS_KEY_ID', true) && env.get('SQS_SECRET_ACCESS_KEY', true)) {
sqsConfig.credentials = {
accessKeyId: env.get('SQS_ACCESS_KEY_ID', true),
secretAccessKey: env.get('SQS_SECRET_ACCESS_KEY', true),
}
}
return new SQSClient(sqsConfig)
})
container.bind<S3Client | undefined>(TYPES.S3).toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.Env)
let s3Client = undefined
if (env.get('S3_AWS_REGION', true)) {
s3Client = new S3Client({
apiVersion: 'latest',
region: env.get('S3_AWS_REGION', true),
})
}
return s3Client
})
// env vars
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL'))
container.bind(TYPES.EXTENSIONS_SERVER_URL).toConstantValue(env.get('EXTENSIONS_SERVER_URL', true))
container.bind(TYPES.AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))
container.bind(TYPES.S3_AWS_REGION).toConstantValue(env.get('S3_AWS_REGION', true))
container.bind(TYPES.S3_BACKUP_BUCKET_NAME).toConstantValue(env.get('S3_BACKUP_BUCKET_NAME', true))
container.bind(TYPES.EMAIL_ATTACHMENT_MAX_BYTE_SIZE).toConstantValue(env.get('EMAIL_ATTACHMENT_MAX_BYTE_SIZE'))
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION'))
container
.bind(TYPES.FILE_UPLOAD_PATH)
.toConstantValue(
env.get('FILE_UPLOAD_PATH', true) ? env.get('FILE_UPLOAD_PATH', true) : this.DEFAULT_FILE_UPLOAD_PATH,
)
// Handlers
container
.bind<DuplicateItemSyncedEventHandler>(TYPES.DuplicateItemSyncedEventHandler)
.toDynamicValue((context: interfaces.Context) => {
return new DuplicateItemSyncedEventHandler(
context.container.get(TYPES.ItemRepository),
context.container.get(TYPES.DomainEventFactory),
context.container.get(TYPES.DomainEventPublisher),
context.container.get(TYPES.Logger),
)
})
container
.bind<AccountDeletionRequestedEventHandler>(TYPES.AccountDeletionRequestedEventHandler)
.toDynamicValue((context: interfaces.Context) => {
return new AccountDeletionRequestedEventHandler(
context.container.get(TYPES.ItemRepository),
context.container.get(TYPES.Logger),
)
})
container
.bind<EmailBackupRequestedEventHandler>(TYPES.EmailBackupRequestedEventHandler)
.toDynamicValue((context: interfaces.Context) => {
return new EmailBackupRequestedEventHandler(
context.container.get(TYPES.ItemRepository),
context.container.get(TYPES.AuthHttpService),
context.container.get(TYPES.ItemBackupService),
context.container.get(TYPES.DomainEventPublisher),
context.container.get(TYPES.DomainEventFactory),
context.container.get(TYPES.EMAIL_ATTACHMENT_MAX_BYTE_SIZE),
context.container.get(TYPES.ItemTransferCalculator),
context.container.get(TYPES.S3_BACKUP_BUCKET_NAME),
context.container.get(TYPES.Logger),
)
})
container
.bind<ItemRevisionCreationRequestedEventHandler>(TYPES.ItemRevisionCreationRequestedEventHandler)
.toDynamicValue((context: interfaces.Context) => {
return new ItemRevisionCreationRequestedEventHandler(
context.container.get(TYPES.ItemRepository),
context.container.get(TYPES.ItemBackupService),
context.container.get(TYPES.DomainEventFactory),
context.container.get(TYPES.DomainEventPublisher),
)
})
// Services
container.bind<ContentDecoder>(TYPES.ContentDecoder).toDynamicValue(() => new ContentDecoder())
container.bind<AxiosInstance>(TYPES.HTTPClient).toDynamicValue(() => axios.create())
container.bind<AuthHttpServiceInterface>(TYPES.AuthHttpService).toDynamicValue((context: interfaces.Context) => {
return new AuthHttpService(context.container.get(TYPES.HTTPClient), context.container.get(TYPES.AUTH_SERVER_URL))
})
container
.bind<ExtensionsHttpServiceInterface>(TYPES.ExtensionsHttpService)
.toDynamicValue((context: interfaces.Context) => {
return new ExtensionsHttpService(
context.container.get(TYPES.HTTPClient),
context.container.get(TYPES.ItemRepository),
context.container.get(TYPES.ContentDecoder),
context.container.get(TYPES.DomainEventPublisher),
context.container.get(TYPES.DomainEventFactory),
context.container.get(TYPES.Logger),
)
})
container
.bind<ItemBackupServiceInterface>(TYPES.ItemBackupService)
.toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.Env)
if (env.get('S3_AWS_REGION', true)) {
return new S3ItemBackupService(
context.container.get(TYPES.S3_BACKUP_BUCKET_NAME),
context.container.get(TYPES.ItemProjector),
context.container.get(TYPES.Logger),
context.container.get(TYPES.S3),
)
} else {
return new FSItemBackupService(
context.container.get(TYPES.FILE_UPLOAD_PATH),
context.container.get(TYPES.ItemProjector),
context.container.get(TYPES.Logger),
)
}
})
container
.bind<DomainEventMessageHandlerInterface>(TYPES.DomainEventMessageHandler)
.toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.Env)
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['DUPLICATE_ITEM_SYNCED', context.container.get(TYPES.DuplicateItemSyncedEventHandler)],
['ACCOUNT_DELETION_REQUESTED', context.container.get(TYPES.AccountDeletionRequestedEventHandler)],
['EMAIL_BACKUP_REQUESTED', context.container.get(TYPES.EmailBackupRequestedEventHandler)],
['ITEM_REVISION_CREATION_REQUESTED', context.container.get(TYPES.ItemRevisionCreationRequestedEventHandler)],
])
const handler =
env.get('NEW_RELIC_ENABLED', true) === 'true'
? new SQSNewRelicEventMessageHandler(eventHandlers, context.container.get(TYPES.Logger))
: new SQSEventMessageHandler(eventHandlers, context.container.get(TYPES.Logger))
return handler
})
container
.bind<DomainEventSubscriberFactoryInterface>(TYPES.DomainEventSubscriberFactory)
.toDynamicValue((context: interfaces.Context) => {
return new SQSDomainEventSubscriberFactory(
context.container.get(TYPES.SQS),
context.container.get(TYPES.SQS_QUEUE_URL),
context.container.get(TYPES.DomainEventMessageHandler),
)
})
return container
}
}

View file

@ -0,0 +1 @@
export * from './Service'

View file

@ -1,7 +1,7 @@
import { controller, httpGet } from 'inversify-express-utils'
@controller('/healthcheck')
export class HealthCheckController {
export class InversifyExpressHealthCheckController {
@httpGet('/')
public async get(): Promise<string> {
return 'OK'

View file

@ -3,20 +3,21 @@ import 'reflect-metadata'
import * as express from 'express'
import { ContentType } from '@standardnotes/common'
import { ItemsController } from './ItemsController'
import { InversifyExpressItemsController } from './InversifyExpressItemsController'
import { results } from 'inversify-express-utils'
import { SyncItems } from '../Domain/UseCase/SyncItems'
import { ApiVersion } from '../Domain/Api/ApiVersion'
import { SyncResponseFactoryResolverInterface } from '../Domain/Item/SyncResponse/SyncResponseFactoryResolverInterface'
import { SyncResponseFactoryInterface } from '../Domain/Item/SyncResponse/SyncResponseFactoryInterface'
import { SyncResponse20200115 } from '../Domain/Item/SyncResponse/SyncResponse20200115'
import { CheckIntegrity } from '../Domain/UseCase/CheckIntegrity/CheckIntegrity'
import { GetItem } from '../Domain/UseCase/GetItem/GetItem'
import { Item } from '../Domain/Item/Item'
import { ProjectorInterface } from '../Projection/ProjectorInterface'
import { ItemProjection } from '../Projection/ItemProjection'
import { Item } from '../../Domain/Item/Item'
import { ItemProjection } from '../../Projection/ItemProjection'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { ApiVersion } from '../../Domain/Api/ApiVersion'
import { SyncResponse20200115 } from '../../Domain/Item/SyncResponse/SyncResponse20200115'
import { SyncResponseFactoryInterface } from '../../Domain/Item/SyncResponse/SyncResponseFactoryInterface'
import { SyncResponseFactoryResolverInterface } from '../../Domain/Item/SyncResponse/SyncResponseFactoryResolverInterface'
import { CheckIntegrity } from '../../Domain/UseCase/CheckIntegrity/CheckIntegrity'
import { GetItem } from '../../Domain/UseCase/GetItem/GetItem'
import { SyncItems } from '../../Domain/UseCase/SyncItems'
import { ControllerContainerInterface } from '@standardnotes/domain-core'
describe('ItemsController', () => {
describe('InversifyExpressItemsController', () => {
let syncItems: SyncItems
let checkIntegrity: CheckIntegrity
let getItem: GetItem
@ -26,11 +27,22 @@ describe('ItemsController', () => {
let syncResponceFactoryResolver: SyncResponseFactoryResolverInterface
let syncResponseFactory: SyncResponseFactoryInterface
let syncResponse: SyncResponse20200115
let controllerContainer: ControllerContainerInterface
const createController = () =>
new ItemsController(syncItems, checkIntegrity, getItem, itemProjector, syncResponceFactoryResolver)
new InversifyExpressItemsController(
syncItems,
checkIntegrity,
getItem,
itemProjector,
syncResponceFactoryResolver,
controllerContainer,
)
beforeEach(() => {
controllerContainer = {} as jest.Mocked<ControllerContainerInterface>
controllerContainer.register = jest.fn()
itemProjector = {} as jest.Mocked<ProjectorInterface<Item, ItemProjection>>
itemProjector.projectFull = jest.fn().mockReturnValue({ foo: 'bar' })

View file

@ -1,27 +1,34 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, httpPost, results } from 'inversify-express-utils'
import TYPES from '../Bootstrap/Types'
import { ApiVersion } from '../Domain/Api/ApiVersion'
import { Item } from '../Domain/Item/Item'
import { SyncResponseFactoryResolverInterface } from '../Domain/Item/SyncResponse/SyncResponseFactoryResolverInterface'
import { CheckIntegrity } from '../Domain/UseCase/CheckIntegrity/CheckIntegrity'
import { GetItem } from '../Domain/UseCase/GetItem/GetItem'
import { SyncItems } from '../Domain/UseCase/SyncItems'
import { ItemProjection } from '../Projection/ItemProjection'
import { ProjectorInterface } from '../Projection/ProjectorInterface'
@controller('/items', TYPES.AuthMiddleware)
export class ItemsController extends BaseHttpController {
import TYPES from '../../Bootstrap/Types'
import { Item } from '../../Domain/Item/Item'
import { SyncResponseFactoryResolverInterface } from '../../Domain/Item/SyncResponse/SyncResponseFactoryResolverInterface'
import { CheckIntegrity } from '../../Domain/UseCase/CheckIntegrity/CheckIntegrity'
import { GetItem } from '../../Domain/UseCase/GetItem/GetItem'
import { SyncItems } from '../../Domain/UseCase/SyncItems'
import { ItemProjection } from '../../Projection/ItemProjection'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { ApiVersion } from '../../Domain/Api/ApiVersion'
import { ControllerContainerInterface } from '@standardnotes/domain-core'
@controller('/items', TYPES.Sync_AuthMiddleware)
export class InversifyExpressItemsController extends BaseHttpController {
constructor(
@inject(TYPES.SyncItems) private syncItems: SyncItems,
@inject(TYPES.CheckIntegrity) private checkIntegrity: CheckIntegrity,
@inject(TYPES.GetItem) private getItem: GetItem,
@inject(TYPES.ItemProjector) private itemProjector: ProjectorInterface<Item, ItemProjection>,
@inject(TYPES.SyncResponseFactoryResolver)
@inject(TYPES.Sync_SyncItems) private syncItems: SyncItems,
@inject(TYPES.Sync_CheckIntegrity) private checkIntegrity: CheckIntegrity,
@inject(TYPES.Sync_GetItem) private getItem: GetItem,
@inject(TYPES.Sync_ItemProjector) private itemProjector: ProjectorInterface<Item, ItemProjection>,
@inject(TYPES.Sync_SyncResponseFactoryResolver)
private syncResponseFactoryResolver: SyncResponseFactoryResolverInterface,
@inject(TYPES.Sync_ControllerContainer) private controllerContainer: ControllerContainerInterface,
) {
super()
this.controllerContainer.register('sync.items.sync', this.sync.bind(this))
this.controllerContainer.register('sync.items.check_integrity', this.checkItemsIntegrity.bind(this))
this.controllerContainer.register('sync.items.get_item', this.getSingleItem.bind(this))
}
@httpPost('/sync')

View file

@ -2,19 +2,19 @@ import 'reflect-metadata'
import * as winston from 'winston'
import { AuthMiddleware } from './AuthMiddleware'
import { InversifyExpressAuthMiddleware } from './InversifyExpressAuthMiddleware'
import { NextFunction, Request, Response } from 'express'
import { sign } from 'jsonwebtoken'
import { RoleName } from '@standardnotes/domain-core'
describe('AuthMiddleware', () => {
describe('InversifyExpressAuthMiddleware', () => {
let logger: winston.Logger
const jwtSecret = 'auth_jwt_secret'
let request: Request
let response: Response
let next: NextFunction
const createMiddleware = () => new AuthMiddleware(jwtSecret, logger)
const createMiddleware = () => new InversifyExpressAuthMiddleware(jwtSecret, logger)
beforeEach(() => {
logger = {} as jest.Mocked<winston.Logger>

View file

@ -5,7 +5,7 @@ import { CrossServiceTokenData } from '@standardnotes/security'
import * as winston from 'winston'
import { RoleName } from '@standardnotes/domain-core'
export class AuthMiddleware extends BaseMiddleware {
export class InversifyExpressAuthMiddleware extends BaseMiddleware {
constructor(private authJWTSecret: string, private logger: winston.Logger) {
super()
}

View file

@ -0,0 +1 @@
export * from './Bootstrap'

View file

@ -4249,6 +4249,7 @@ __metadata:
"@standardnotes/auth-server": "workspace:^"
"@standardnotes/domain-core": "workspace:^"
"@standardnotes/domain-events-infra": "workspace:^"
"@standardnotes/syncing-server": "workspace:^"
"@types/cors": "npm:^2.8.9"
"@types/express": "npm:^4.17.14"
"@types/prettyjson": "npm:^0.0.30"
@ -4483,7 +4484,7 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/syncing-server@workspace:packages/syncing-server":
"@standardnotes/syncing-server@workspace:^, @standardnotes/syncing-server@workspace:packages/syncing-server":
version: 0.0.0-use.local
resolution: "@standardnotes/syncing-server@workspace:packages/syncing-server"
dependencies: