Prechádzať zdrojové kódy

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
Karol Sójko 2 rokov pred
rodič
commit
b3b617ea0b
31 zmenil súbory, kde vykonal 778 pridanie a 672 odobranie
  1. 6 5
      .pnp.cjs
  2. 5 19
      packages/api-gateway/src/Bootstrap/Service.ts
  3. 1 1
      packages/api-gateway/src/Controller/AuthMiddleware.ts
  4. 1 1
      packages/api-gateway/src/Controller/SubscriptionTokenAuthMiddleware.ts
  5. 1 1
      packages/api-gateway/src/Controller/WebSocketAuthMiddleware.ts
  6. 4 4
      packages/api-gateway/src/Controller/v1/ItemsController.ts
  7. 1 1
      packages/api-gateway/src/Controller/v1/UsersController.ts
  8. 1 1
      packages/api-gateway/src/Service/Http/HttpServiceProxy.ts
  9. 6 1
      packages/api-gateway/src/Service/Proxy/DirectCallServiceProxy.ts
  10. 4 0
      packages/api-gateway/src/Service/Resolver/EndpointResolver.ts
  11. 1 1
      packages/auth/src/Bootstrap/Container.ts
  12. 1 1
      packages/auth/src/Bootstrap/Service.ts
  13. 8 10
      packages/home-server/bin/server.ts
  14. 1 0
      packages/home-server/package.json
  15. 6 6
      packages/syncing-server/bin/server.ts
  16. 6 4
      packages/syncing-server/bin/worker.ts
  17. 0 119
      packages/syncing-server/src/Bootstrap/CommonContainerConfigLoader.ts
  18. 516 0
      packages/syncing-server/src/Bootstrap/Container.ts
  19. 48 41
      packages/syncing-server/src/Bootstrap/DataSource.ts
  20. 0 154
      packages/syncing-server/src/Bootstrap/ServerContainerConfigLoader.ts
  21. 42 0
      packages/syncing-server/src/Bootstrap/Service.ts
  22. 61 59
      packages/syncing-server/src/Bootstrap/Types.ts
  23. 0 207
      packages/syncing-server/src/Bootstrap/WorkerContainerConfigLoader.ts
  24. 1 0
      packages/syncing-server/src/Bootstrap/index.ts
  25. 1 1
      packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressHealthCheckController.ts
  26. 26 14
      packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressItemsController.spec.ts
  27. 23 16
      packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressItemsController.ts
  28. 3 3
      packages/syncing-server/src/Infra/InversifyExpressUtils/Middleware/InversifyExpressAuthMiddleware.spec.ts
  29. 1 1
      packages/syncing-server/src/Infra/InversifyExpressUtils/Middleware/InversifyExpressAuthMiddleware.ts
  30. 1 0
      packages/syncing-server/src/index.ts
  31. 2 1
      yarn.lock

+ 6 - 5
.pnp.cjs

@@ -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"],\

+ 5 - 19
packages/api-gateway/src/Bootstrap/Service.ts

@@ -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> {

+ 1 - 1
packages/api-gateway/src/Controller/AuthMiddleware.ts

@@ -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

+ 1 - 1
packages/api-gateway/src/Controller/SubscriptionTokenAuthMiddleware.ts

@@ -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
   }
 }

+ 1 - 1
packages/api-gateway/src/Controller/WebSocketAuthMiddleware.ts

@@ -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

+ 4 - 4
packages/api-gateway/src/Controller/v1/ItemsController.ts

@@ -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),

+ 1 - 1
packages/api-gateway/src/Controller/v1/UsersController.ts

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

+ 1 - 1
packages/api-gateway/src/Service/Http/HttpServiceProxy.ts

@@ -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: {

+ 6 - 1
packages/api-gateway/src/Service/Proxy/DirectCallServiceProxy.ts

@@ -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(

+ 4 - 0
packages/api-gateway/src/Service/Resolver/EndpointResolver.ts

@@ -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 {

+ 1 - 1
packages/auth/src/Bootstrap/Container.ts

@@ -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)

+ 1 - 1
packages/auth/src/Bootstrap/Service.ts

@@ -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> {

+ 8 - 10
packages/home-server/bin/server.ts

@@ -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}`)
+})

+ 1 - 0
packages/home-server/package.json

@@ -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",

+ 6 - 6
packages/syncing-server/bin/server.ts

@@ -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) => {

+ 6 - 4
packages/syncing-server/bin/worker.ts

@@ -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)

+ 0 - 119
packages/syncing-server/src/Bootstrap/CommonContainerConfigLoader.ts

@@ -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
-  }
-}

+ 516 - 0
packages/syncing-server/src/Bootstrap/Container.ts

@@ -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
+  }
+}

+ 48 - 41
packages/syncing-server/src/Bootstrap/DataSource.ts

@@ -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',
+}
+
+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'),
-  },
-  slaves: [
-    {
-      host: env.get('DB_REPLICA_HOST', true),
+  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 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 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`,
+  }
 
-const sqliteDataSourceOptions: SqliteConnectionOptions = {
-  ...commonDataSourceOptions,
-  type: 'sqlite',
-  database: `data/${env.get('DB_DATABASE')}.sqlite`,
+  dataSource = new DataSource(sqliteDataSourceOptions)
 }
 
-export const AppDataSource = new DataSource(isConfiguredForMySQL ? mySQLDataSourceOptions : sqliteDataSourceOptions)
+export const AppDataSource = dataSource

+ 0 - 154
packages/syncing-server/src/Bootstrap/ServerContainerConfigLoader.ts

@@ -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
-  }
-}

+ 42 - 0
packages/syncing-server/src/Bootstrap/Service.ts

@@ -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()
+  }
+}

+ 61 - 59
packages/syncing-server/src/Bootstrap/Types.ts

@@ -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

+ 0 - 207
packages/syncing-server/src/Bootstrap/WorkerContainerConfigLoader.ts

@@ -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
-  }
-}

+ 1 - 0
packages/syncing-server/src/Bootstrap/index.ts

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

+ 1 - 1
packages/syncing-server/src/Controller/HealthCheckController.ts → packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressHealthCheckController.ts

@@ -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'

+ 26 - 14
packages/syncing-server/src/Controller/ItemsController.spec.ts → packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressItemsController.spec.ts

@@ -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'
-
-describe('ItemsController', () => {
+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('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' })
 

+ 23 - 16
packages/syncing-server/src/Controller/ItemsController.ts → packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressItemsController.ts

@@ -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')

+ 3 - 3
packages/syncing-server/src/Controller/AuthMiddleware.spec.ts → packages/syncing-server/src/Infra/InversifyExpressUtils/Middleware/InversifyExpressAuthMiddleware.spec.ts

@@ -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>

+ 1 - 1
packages/syncing-server/src/Controller/AuthMiddleware.ts → packages/syncing-server/src/Infra/InversifyExpressUtils/Middleware/InversifyExpressAuthMiddleware.ts

@@ -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()
   }

+ 1 - 0
packages/syncing-server/src/index.ts

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

+ 2 - 1
yarn.lock

@@ -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: