Bläddra i källkod

feat(syncing-server): refactor container config into server and worker (#443)

* feat(syncing-server): refactor container config into server and worker

* fix(syncing-server): yarn lock

* fix(api-gateway): add client update response on v1 revision endpoints

* fix(syncing-server): linter issue
Karol Sójko 2 år sedan
förälder
incheckning
993d31167b
67 ändrade filer med 607 tillägg och 1685 borttagningar
  1. 1 3
      .pnp.cjs
  2. 26 20
      packages/api-gateway/src/Controller/v1/RevisionsController.ts
  3. 2 2
      packages/syncing-server/bin/content.ts
  4. 0 136
      packages/syncing-server/bin/revisions.ts
  5. 2 3
      packages/syncing-server/bin/server.ts
  6. 2 2
      packages/syncing-server/bin/worker.ts
  7. 0 5
      packages/syncing-server/docker/entrypoint.sh
  8. 0 3
      packages/syncing-server/package.json
  9. 108 0
      packages/syncing-server/src/Bootstrap/CommonContainerConfigLoader.ts
  10. 0 331
      packages/syncing-server/src/Bootstrap/Container.ts
  11. 1 2
      packages/syncing-server/src/Bootstrap/DataSource.ts
  12. 0 2
      packages/syncing-server/src/Bootstrap/Env.ts
  13. 164 0
      packages/syncing-server/src/Bootstrap/ServerContainerConfigLoader.ts
  14. 1 7
      packages/syncing-server/src/Bootstrap/Types.ts
  15. 225 0
      packages/syncing-server/src/Bootstrap/WorkerContainerConfigLoader.ts
  16. 1 8
      packages/syncing-server/src/Controller/AuthMiddleware.ts
  17. 0 99
      packages/syncing-server/src/Controller/RevisionsController.spec.ts
  18. 0 85
      packages/syncing-server/src/Controller/RevisionsController.ts
  19. 1 23
      packages/syncing-server/src/Domain/Event/DomainEventFactory.ts
  20. 0 5
      packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts
  21. 7 9
      packages/syncing-server/src/Domain/Extension/ExtensionsHttpService.ts
  22. 1 1
      packages/syncing-server/src/Domain/Handler/AccountDeletionRequestedEventHandler.spec.ts
  23. 1 7
      packages/syncing-server/src/Domain/Handler/AccountDeletionRequestedEventHandler.ts
  24. 6 9
      packages/syncing-server/src/Domain/Handler/CloudBackupRequestedEventHandler.ts
  25. 4 7
      packages/syncing-server/src/Domain/Handler/DuplicateItemSyncedEventHandler.ts
  26. 9 12
      packages/syncing-server/src/Domain/Handler/EmailBackupRequestedEventHandler.ts
  27. 4 7
      packages/syncing-server/src/Domain/Handler/ItemRevisionCreationRequestedEventHandler.ts
  28. 0 73
      packages/syncing-server/src/Domain/Handler/UserContentSizeRecalculationRequestedEventHandler.ts
  29. 0 2
      packages/syncing-server/src/Domain/Item/ContentDecoder.ts
  30. 1 10
      packages/syncing-server/src/Domain/Item/Item.ts
  31. 1 7
      packages/syncing-server/src/Domain/Item/ItemFactory.ts
  32. 12 15
      packages/syncing-server/src/Domain/Item/ItemService.ts
  33. 1 8
      packages/syncing-server/src/Domain/Item/ItemTransferCalculator.ts
  34. 0 2
      packages/syncing-server/src/Domain/Item/SaveRule/ContentFilter.ts
  35. 1 2
      packages/syncing-server/src/Domain/Item/SaveRule/ContentTypeFilter.ts
  36. 0 2
      packages/syncing-server/src/Domain/Item/SaveRule/OwnershipFilter.ts
  37. 2 4
      packages/syncing-server/src/Domain/Item/SaveRule/TimeDifferenceFilter.ts
  38. 0 2
      packages/syncing-server/src/Domain/Item/SaveRule/UuidFilter.ts
  39. 0 2
      packages/syncing-server/src/Domain/Item/SaveValidator/ItemSaveValidator.ts
  40. 1 4
      packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20161215.ts
  41. 2 6
      packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20200115.ts
  42. 2 5
      packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactoryResolver.ts
  43. 0 44
      packages/syncing-server/src/Domain/Map/RevisionMetadataMap.ts
  44. 0 7
      packages/syncing-server/src/Domain/Revision/Revision.spec.ts
  45. 0 81
      packages/syncing-server/src/Domain/Revision/Revision.ts
  46. 0 28
      packages/syncing-server/src/Domain/Revision/RevisionMetadata.ts
  47. 0 5
      packages/syncing-server/src/Domain/Revision/RevisionMetadataProps.ts
  48. 0 10
      packages/syncing-server/src/Domain/Revision/RevisionRepositoryInterface.ts
  49. 0 193
      packages/syncing-server/src/Domain/Revision/RevisionService.spec.ts
  50. 0 89
      packages/syncing-server/src/Domain/Revision/RevisionService.ts
  51. 0 14
      packages/syncing-server/src/Domain/Revision/RevisionServiceInterface.ts
  52. 1 4
      packages/syncing-server/src/Domain/UseCase/CheckIntegrity/CheckIntegrity.ts
  53. 1 4
      packages/syncing-server/src/Domain/UseCase/GetItem/GetItem.ts
  54. 1 4
      packages/syncing-server/src/Domain/UseCase/SyncItems.ts
  55. 3 6
      packages/syncing-server/src/Infra/FS/FSItemBackupService.ts
  56. 2 7
      packages/syncing-server/src/Infra/HTTP/AuthHttpService.ts
  57. 1 7
      packages/syncing-server/src/Infra/MySQL/MySQLItemRepository.ts
  58. 0 83
      packages/syncing-server/src/Infra/MySQL/MySQLRevisionRepository.ts
  59. 4 7
      packages/syncing-server/src/Infra/S3/S3ItemBackupService.ts
  60. 1 4
      packages/syncing-server/src/Projection/ItemConflictProjector.ts
  61. 1 4
      packages/syncing-server/src/Projection/ItemProjector.ts
  62. 0 15
      packages/syncing-server/src/Projection/RevisionProjection.ts
  63. 0 74
      packages/syncing-server/src/Projection/RevisionProjector.spec.ts
  64. 0 47
      packages/syncing-server/src/Projection/RevisionProjector.ts
  65. 3 5
      packages/syncing-server/src/Projection/SavedItemProjector.ts
  66. 0 9
      packages/syncing-server/src/Projection/SimpleRevisionProjection.ts
  67. 0 2
      yarn.lock

+ 1 - 3
.pnp.cjs

@@ -4687,7 +4687,6 @@ const RAW_RUNTIME_STATE =
           ["@types/dotenv", "npm:8.2.0"],\
           ["@types/express", "npm:4.17.14"],\
           ["@types/inversify-express-utils", "npm:2.0.0"],\
-          ["@types/ioredis", "npm:5.0.0"],\
           ["@types/jest", "npm:29.1.1"],\
           ["@types/jsonwebtoken", "npm:9.0.1"],\
           ["@types/newrelic", "npm:9.4.0"],\
@@ -4704,7 +4703,6 @@ const RAW_RUNTIME_STATE =
           ["helmet", "npm:6.0.0"],\
           ["inversify", "npm:6.0.1"],\
           ["inversify-express-utils", "npm:6.4.3"],\
-          ["ioredis", "npm:5.2.4"],\
           ["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.2"],\
           ["jsonwebtoken", "npm:9.0.0"],\
           ["mysql2", "npm:3.0.1"],\
@@ -4714,7 +4712,7 @@ const RAW_RUNTIME_STATE =
           ["prettyjson", "npm:1.2.5"],\
           ["reflect-metadata", "npm:0.1.13"],\
           ["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.0.3"],\
-          ["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.10"],\
+          ["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.10"],\
           ["typescript", "patch:typescript@npm%3A4.8.4#optional!builtin<compat/typescript>::version=4.8.4&hash=701156"],\
           ["ua-parser-js", "npm:1.0.32"],\
           ["uuid", "npm:9.0.0"],\

+ 26 - 20
packages/api-gateway/src/Controller/v1/RevisionsController.ts

@@ -1,35 +1,41 @@
-import { Request, Response } from 'express'
-import { inject } from 'inversify'
-import { BaseHttpController, controller, httpDelete, httpGet } from 'inversify-express-utils'
+import { BaseHttpController, controller, httpDelete, httpGet, results } from 'inversify-express-utils'
 import TYPES from '../../Bootstrap/Types'
-import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
 
 @controller('/v1/items/:item_id/revisions', TYPES.AuthMiddleware)
 export class RevisionsController extends BaseHttpController {
-  constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
-    super()
-  }
-
   @httpGet('/')
-  async getRevisions(request: Request, response: Response): Promise<void> {
-    await this.httpService.callSyncingServer(request, response, `items/${request.params.item_id}/revisions`)
+  async getRevisions(): Promise<results.JsonResult> {
+    return this.json(
+      {
+        error: {
+          message: 'Please update your client application.',
+        },
+      },
+      410,
+    )
   }
 
   @httpGet('/:id')
-  async getRevision(request: Request, response: Response): Promise<void> {
-    await this.httpService.callSyncingServer(
-      request,
-      response,
-      `items/${request.params.item_id}/revisions/${request.params.id}`,
+  async getRevision(): Promise<results.JsonResult> {
+    return this.json(
+      {
+        error: {
+          message: 'Please update your client application.',
+        },
+      },
+      410,
     )
   }
 
   @httpDelete('/:id')
-  async deleteRevision(request: Request, response: Response): Promise<void> {
-    await this.httpService.callSyncingServer(
-      request,
-      response,
-      `items/${request.params.item_id}/revisions/${request.params.id}`,
+  async deleteRevision(): Promise<results.JsonResult> {
+    return this.json(
+      {
+        error: {
+          message: 'Please update your client application.',
+        },
+      },
+      410,
     )
   }
 }

+ 2 - 2
packages/syncing-server/bin/content.ts

@@ -4,11 +4,11 @@ import 'newrelic'
 
 import { Logger } from 'winston'
 
-import { ContainerConfigLoader } from '../src/Bootstrap/Container'
 import TYPES from '../src/Bootstrap/Types'
 import { Env } from '../src/Bootstrap/Env'
 import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
 import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
+import { WorkerContainerConfigLoader } from '../src/Bootstrap/WorkerContainerConfigLoader'
 
 const inputArgs = process.argv.slice(2)
 const userUuid = inputArgs[0]
@@ -20,7 +20,7 @@ const fixContentSize = async (
   await domainEventPublisher.publish(domainEventFactory.createUserContentSizeRecalculationRequestedEvent(userUuid))
 }
 
-const container = new ContainerConfigLoader()
+const container = new WorkerContainerConfigLoader()
 void container.load().then((container) => {
   const env: Env = new Env()
   env.load()

+ 0 - 136
packages/syncing-server/bin/revisions.ts

@@ -1,136 +0,0 @@
-import 'reflect-metadata'
-
-import 'newrelic'
-
-import { Logger } from 'winston'
-
-import { ContainerConfigLoader } from '../src/Bootstrap/Container'
-import TYPES from '../src/Bootstrap/Types'
-import { Env } from '../src/Bootstrap/Env'
-import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
-import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
-import { ItemRepositoryInterface } from '../src/Domain/Item/ItemRepositoryInterface'
-import { ContentType } from '@standardnotes/common'
-
-const fixRevisionsOwnership = async (
-  year: number,
-  month: number,
-  revisionsProcessingLimit: number,
-  itemRepository: ItemRepositoryInterface,
-  domainEventFactory: DomainEventFactoryInterface,
-  domainEventPublisher: DomainEventPublisherInterface,
-  logger: Logger,
-): Promise<void> => {
-  const createdAfter = new Date(`${year}-${month}-1`)
-  const createdBefore = new Date(`${month !== 12 ? year : year + 1}-${month !== 12 ? month + 1 : 1}-1`)
-
-  logger.info(`[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] Processing items`)
-
-  const itemsCount = await itemRepository.countAll({
-    createdBetween: [createdAfter, createdBefore],
-    selectString: 'item.uuid as uuid, item.user_uuid as userUuid',
-    contentType: [ContentType.Note, ContentType.File],
-    sortOrder: 'ASC',
-    sortBy: 'uuid',
-  })
-
-  logger.info(
-    `[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] There are ${itemsCount} items to process.`,
-  )
-
-  const amountOfPages = Math.ceil(itemsCount / revisionsProcessingLimit)
-  const tenPercentOfPages = Math.ceil(amountOfPages / 10)
-  let itemsProcessedCounter = 0
-  let itemsSkippedCounter = 0
-  for (let page = 1; page <= amountOfPages; page++) {
-    if (page % tenPercentOfPages === 0) {
-      logger.info(
-        `[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] Processing page ${page}/${amountOfPages} of items.`,
-      )
-      logger.info(
-        `[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] Processed successfully/skipped items: ${itemsProcessedCounter}/${itemsSkippedCounter}.`,
-      )
-    }
-
-    const items = await itemRepository.findAllRaw<{ uuid: string; userUuid: string }>({
-      createdBetween: [createdAfter, createdBefore],
-      selectString: 'item.uuid as uuid, item.user_uuid as userUuid',
-      contentType: [ContentType.Note, ContentType.File],
-      offset: (page - 1) * revisionsProcessingLimit,
-      limit: revisionsProcessingLimit,
-      sortOrder: 'ASC',
-      sortBy: 'uuid',
-    })
-
-    if (items.length === 0) {
-      logger.warn(
-        `[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] No items fetched for offset ${
-          (page - 1) * revisionsProcessingLimit
-        } and limit ${revisionsProcessingLimit}.`,
-      )
-    }
-
-    for (const item of items) {
-      if (!item.userUuid || !item.uuid) {
-        itemsSkippedCounter++
-        continue
-      }
-
-      await domainEventPublisher.publish(
-        domainEventFactory.createRevisionsOwnershipUpdateRequestedEvent({
-          userUuid: item.userUuid,
-          itemUuid: item.uuid,
-        }),
-      )
-
-      itemsProcessedCounter++
-    }
-  }
-}
-
-const container = new ContainerConfigLoader()
-void container.load().then((container) => {
-  const env: Env = new Env()
-  env.load()
-
-  const logger: Logger = container.get(TYPES.Logger)
-
-  logger.info('Starting revisions ownership fixing')
-
-  const itemRepository: ItemRepositoryInterface = container.get(TYPES.ItemRepository)
-  const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
-  const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
-
-  const years = env.get('REVISION_YEARS').split(',')
-  const months = env.get('REVISION_MONTHS').split(',')
-  const revisionsProcessingLimit = env.get('REVISIONS_PROCESSING_LIMIT')
-
-  const promises = []
-  for (const year of years) {
-    for (const month of months) {
-      promises.push(
-        fixRevisionsOwnership(
-          +year,
-          +month,
-          +revisionsProcessingLimit,
-          itemRepository,
-          domainEventFactory,
-          domainEventPublisher,
-          logger,
-        ),
-      )
-    }
-  }
-
-  Promise.all(promises)
-    .then(() => {
-      logger.info('revisions ownership fix complete.')
-
-      process.exit(0)
-    })
-    .catch((error) => {
-      logger.error(`Could not finish revisions ownership fix: ${error.message}`)
-
-      process.exit(1)
-    })
-})

+ 2 - 3
packages/syncing-server/bin/server.ts

@@ -6,7 +6,6 @@ import * as Sentry from '@sentry/node'
 import * as Tracing from '@sentry/tracing'
 
 import '../src/Controller/HealthCheckController'
-import '../src/Controller/RevisionsController'
 import '../src/Controller/ItemsController'
 
 import helmet from 'helmet'
@@ -15,11 +14,11 @@ import { urlencoded, json, Request, Response, NextFunction, ErrorRequestHandler
 import * as winston from 'winston'
 
 import { InversifyExpressServer } from 'inversify-express-utils'
-import { ContainerConfigLoader } from '../src/Bootstrap/Container'
 import TYPES from '../src/Bootstrap/Types'
 import { Env } from '../src/Bootstrap/Env'
+import { ServerContainerConfigLoader } from '../src/Bootstrap/ServerContainerConfigLoader'
 
-const container = new ContainerConfigLoader()
+const container = new ServerContainerConfigLoader()
 void container.load().then((container) => {
   const env: Env = new Env()
   env.load()

+ 2 - 2
packages/syncing-server/bin/worker.ts

@@ -4,12 +4,12 @@ import 'newrelic'
 
 import { Logger } from 'winston'
 
-import { ContainerConfigLoader } from '../src/Bootstrap/Container'
 import TYPES from '../src/Bootstrap/Types'
 import { Env } from '../src/Bootstrap/Env'
 import { DomainEventSubscriberFactoryInterface } from '@standardnotes/domain-events'
+import { WorkerContainerConfigLoader } from '../src/Bootstrap/WorkerContainerConfigLoader'
 
-const container = new ContainerConfigLoader()
+const container = new WorkerContainerConfigLoader()
 void container.load().then((container) => {
   const env: Env = new Env()
   env.load()

+ 0 - 5
packages/syncing-server/docker/entrypoint.sh

@@ -25,11 +25,6 @@ case "$COMMAND" in
     yarn workspace @standardnotes/syncing-server content-size $USER_UUID
     ;;
 
-  'revisions-ownership-fix' )
-    echo "[Docker] Starting Revisions Ownership Fixing..."
-    yarn workspace @standardnotes/syncing-server revisions-ownership
-    ;;
-
    * )
     echo "[Docker] Unknown command"
     ;;

+ 0 - 3
packages/syncing-server/package.json

@@ -28,7 +28,6 @@
     "worker": "yarn node dist/bin/worker.js",
     "supervisor:worker": "yarn wait-for:syncing-server && yarn node dist/bin/worker.js",
     "content-size": "yarn node dist/bin/content.js",
-    "revisions-ownership": "yarn node dist/bin/revisions.js",
     "upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
   },
   "dependencies": {
@@ -54,7 +53,6 @@
     "helmet": "^6.0.0",
     "inversify": "^6.0.1",
     "inversify-express-utils": "^6.4.3",
-    "ioredis": "^5.2.4",
     "jsonwebtoken": "^9.0.0",
     "mysql2": "^3.0.1",
     "newrelic": "^9.8.0",
@@ -71,7 +69,6 @@
     "@types/dotenv": "^8.2.0",
     "@types/express": "^4.17.14",
     "@types/inversify-express-utils": "^2.0.0",
-    "@types/ioredis": "^5.0.0",
     "@types/jest": "^29.1.1",
     "@types/jsonwebtoken": "^9.0.1",
     "@types/newrelic": "^9.4.0",

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

@@ -0,0 +1,108 @@
+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 { MySQLItemRepository } from '../Infra/MySQL/MySQLItemRepository'
+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'
+// 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 MySQLItemRepository(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))
+      })
+
+    return container
+  }
+}

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

@@ -1,331 +0,0 @@
-import * as winston from 'winston'
-import Redis from 'ioredis'
-import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
-import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
-import { S3Client } from '@aws-sdk/client-s3'
-import { Container } from 'inversify'
-import {
-  DomainEventHandlerInterface,
-  DomainEventMessageHandlerInterface,
-  DomainEventSubscriberFactoryInterface,
-} from '@standardnotes/domain-events'
-
-import { Env } from './Env'
-import TYPES from './Types'
-import { AuthMiddleware } from '../Controller/AuthMiddleware'
-import { MySQLRevisionRepository } from '../Infra/MySQL/MySQLRevisionRepository'
-import { Item } from '../Domain/Item/Item'
-import { Revision } from '../Domain/Revision/Revision'
-import { RevisionProjector } from '../Projection/RevisionProjector'
-import { MySQLItemRepository } from '../Infra/MySQL/MySQLItemRepository'
-import { ContentDecoder } from '../Domain/Item/ContentDecoder'
-import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
-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 { AuthHttpServiceInterface } from '../Domain/Auth/AuthHttpServiceInterface'
-import { AuthHttpService } from '../Infra/HTTP/AuthHttpService'
-import { SyncItems } from '../Domain/UseCase/SyncItems'
-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 { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
-import { RevisionServiceInterface } from '../Domain/Revision/RevisionServiceInterface'
-import { RevisionService } from '../Domain/Revision/RevisionService'
-import { DuplicateItemSyncedEventHandler } from '../Domain/Handler/DuplicateItemSyncedEventHandler'
-import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountDeletionRequestedEventHandler'
-import { ItemProjector } from '../Projection/ItemProjector'
-import { ItemConflictProjector } from '../Projection/ItemConflictProjector'
-import { Timer, TimerInterface } from '@standardnotes/time'
-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'
-// eslint-disable-next-line @typescript-eslint/no-var-requires
-const axios = require('axios')
-import { AxiosInstance } from 'axios'
-import { UuidFilter } from '../Domain/Item/SaveRule/UuidFilter'
-import { ContentTypeFilter } from '../Domain/Item/SaveRule/ContentTypeFilter'
-import { ContentFilter } from '../Domain/Item/SaveRule/ContentFilter'
-import {
-  SNSDomainEventPublisher,
-  SQSDomainEventSubscriberFactory,
-  SQSEventMessageHandler,
-  SQSNewRelicEventMessageHandler,
-} from '@standardnotes/domain-events-infra'
-import { EmailBackupRequestedEventHandler } from '../Domain/Handler/EmailBackupRequestedEventHandler'
-import { CloudBackupRequestedEventHandler } from '../Domain/Handler/CloudBackupRequestedEventHandler'
-import { CheckIntegrity } from '../Domain/UseCase/CheckIntegrity/CheckIntegrity'
-import { GetItem } from '../Domain/UseCase/GetItem/GetItem'
-import { ItemTransferCalculatorInterface } from '../Domain/Item/ItemTransferCalculatorInterface'
-import { ItemTransferCalculator } from '../Domain/Item/ItemTransferCalculator'
-import { ProjectorInterface } from '../Projection/ProjectorInterface'
-import { SavedItemProjection } from '../Projection/SavedItemProjection'
-import { SavedItemProjector } from '../Projection/SavedItemProjector'
-import { ItemProjection } from '../Projection/ItemProjection'
-import { RevisionProjection } from '../Projection/RevisionProjection'
-import { ItemConflict } from '../Domain/Item/ItemConflict'
-import { ItemConflictProjection } from '../Projection/ItemConflictProjection'
-import { AppDataSource } from './DataSource'
-import { RevisionRepositoryInterface } from '../Domain/Revision/RevisionRepositoryInterface'
-import { ItemRepositoryInterface } from '../Domain/Item/ItemRepositoryInterface'
-import { Repository } from 'typeorm'
-import { UserContentSizeRecalculationRequestedEventHandler } from '../Domain/Handler/UserContentSizeRecalculationRequestedEventHandler'
-import { RevisionMetadataMap } from '../Domain/Map/RevisionMetadataMap'
-import { MapperInterface } from '@standardnotes/domain-core'
-import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
-import { SimpleRevisionProjection } from '../Projection/SimpleRevisionProjection'
-import { ItemRevisionCreationRequestedEventHandler } from '../Domain/Handler/ItemRevisionCreationRequestedEventHandler'
-import { FSItemBackupService } from '../Infra/FS/FSItemBackupService'
-
-// 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(): Promise<Container> {
-    const env: Env = new Env()
-    env.load()
-
-    const container = new Container()
-
-    await AppDataSource.initialize()
-
-    const redisUrl = env.get('REDIS_URL')
-    const isRedisInClusterMode = redisUrl.indexOf(',') > 0
-    let redis
-    if (isRedisInClusterMode) {
-      redis = new Redis.Cluster(redisUrl.split(','))
-    } else {
-      redis = new Redis(redisUrl)
-    }
-
-    container.bind(TYPES.Redis).toConstantValue(redis)
-
-    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' })],
-    })
-    container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
-
-    if (env.get('SNS_TOPIC_ARN', true)) {
-      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),
-        }
-      }
-      container.bind<SNSClient>(TYPES.SNS).toConstantValue(new SNSClient(snsConfig))
-    }
-
-    if (env.get('SQS_QUEUE_URL', true)) {
-      const sqsConfig: SQSClientConfig = {
-        region: env.get('SQS_AWS_REGION', true),
-      }
-      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),
-        }
-      }
-      container.bind<SQSClient>(TYPES.SQS).toConstantValue(new SQSClient(sqsConfig))
-    }
-
-    let s3Client = undefined
-    if (env.get('S3_AWS_REGION', true)) {
-      s3Client = new S3Client({
-        apiVersion: 'latest',
-        region: env.get('S3_AWS_REGION', true),
-      })
-    }
-    container.bind<S3Client | undefined>(TYPES.S3).toConstantValue(s3Client)
-
-    // Repositories
-    container.bind<RevisionRepositoryInterface>(TYPES.RevisionRepository).to(MySQLRevisionRepository)
-    container.bind<ItemRepositoryInterface>(TYPES.ItemRepository).to(MySQLItemRepository)
-
-    // ORM
-    container
-      .bind<Repository<Revision>>(TYPES.ORMRevisionRepository)
-      .toConstantValue(AppDataSource.getRepository(Revision))
-    container.bind<Repository<Item>>(TYPES.ORMItemRepository).toConstantValue(AppDataSource.getRepository(Item))
-
-    // Middleware
-    container.bind<AuthMiddleware>(TYPES.AuthMiddleware).to(AuthMiddleware)
-
-    // Projectors
-    container.bind<ProjectorInterface<Revision, RevisionProjection>>(TYPES.RevisionProjector).to(RevisionProjector)
-    container.bind<ProjectorInterface<Item, ItemProjection>>(TYPES.ItemProjector).to(ItemProjector)
-    container.bind<ProjectorInterface<Item, SavedItemProjection>>(TYPES.SavedItemProjector).to(SavedItemProjector)
-    container
-      .bind<ProjectorInterface<ItemConflict, ItemConflictProjection>>(TYPES.ItemConflictProjector)
-      .to(ItemConflictProjector)
-
-    // env vars
-    container.bind(TYPES.REDIS_URL).toConstantValue(env.get('REDIS_URL'))
-    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(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL'))
-    container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
-    container
-      .bind(TYPES.INTERNAL_DNS_REROUTE_ENABLED)
-      .toConstantValue(env.get('INTERNAL_DNS_REROUTE_ENABLED', true) === 'true')
-    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.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,
-      )
-    container
-      .bind(TYPES.FILE_UPLOAD_PATH)
-      .toConstantValue(
-        env.get('FILE_UPLOAD_PATH', true) ? env.get('FILE_UPLOAD_PATH', true) : this.DEFAULT_FILE_UPLOAD_PATH,
-      )
-
-    // use cases
-    container.bind<SyncItems>(TYPES.SyncItems).to(SyncItems)
-    container.bind<CheckIntegrity>(TYPES.CheckIntegrity).to(CheckIntegrity)
-    container.bind<GetItem>(TYPES.GetItem).to(GetItem)
-
-    // Handlers
-    container
-      .bind<DuplicateItemSyncedEventHandler>(TYPES.DuplicateItemSyncedEventHandler)
-      .to(DuplicateItemSyncedEventHandler)
-    container
-      .bind<AccountDeletionRequestedEventHandler>(TYPES.AccountDeletionRequestedEventHandler)
-      .to(AccountDeletionRequestedEventHandler)
-    container
-      .bind<EmailBackupRequestedEventHandler>(TYPES.EmailBackupRequestedEventHandler)
-      .to(EmailBackupRequestedEventHandler)
-    container
-      .bind<CloudBackupRequestedEventHandler>(TYPES.CloudBackupRequestedEventHandler)
-      .to(CloudBackupRequestedEventHandler)
-    container
-      .bind<UserContentSizeRecalculationRequestedEventHandler>(TYPES.UserContentSizeRecalculationRequestedEventHandler)
-      .to(UserContentSizeRecalculationRequestedEventHandler)
-    container
-      .bind<ItemRevisionCreationRequestedEventHandler>(TYPES.ItemRevisionCreationRequestedEventHandler)
-      .to(ItemRevisionCreationRequestedEventHandler)
-
-    // Map
-    container
-      .bind<MapperInterface<RevisionMetadata, SimpleRevisionProjection>>(TYPES.RevisionMetadataMap)
-      .to(RevisionMetadataMap)
-
-    // Services
-    container.bind<ContentDecoder>(TYPES.ContentDecoder).to(ContentDecoder)
-    container.bind<DomainEventFactoryInterface>(TYPES.DomainEventFactory).to(DomainEventFactory)
-    container.bind<AxiosInstance>(TYPES.HTTPClient).toConstantValue(axios.create())
-    container.bind<ItemServiceInterface>(TYPES.ItemService).to(ItemService)
-    container.bind<ItemTransferCalculatorInterface>(TYPES.ItemTransferCalculator).to(ItemTransferCalculator)
-    container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
-    container.bind<SyncResponseFactory20161215>(TYPES.SyncResponseFactory20161215).to(SyncResponseFactory20161215)
-    container.bind<SyncResponseFactory20200115>(TYPES.SyncResponseFactory20200115).to(SyncResponseFactory20200115)
-    container
-      .bind<SyncResponseFactoryResolverInterface>(TYPES.SyncResponseFactoryResolver)
-      .to(SyncResponseFactoryResolver)
-    container.bind<AuthHttpServiceInterface>(TYPES.AuthHttpService).to(AuthHttpService)
-    container.bind<ExtensionsHttpServiceInterface>(TYPES.ExtensionsHttpService).to(ExtensionsHttpService)
-    if (env.get('S3_AWS_REGION', true)) {
-      container.bind<ItemBackupServiceInterface>(TYPES.ItemBackupService).to(S3ItemBackupService)
-    } else {
-      container.bind<ItemBackupServiceInterface>(TYPES.ItemBackupService).to(FSItemBackupService)
-    }
-    container.bind<RevisionServiceInterface>(TYPES.RevisionService).to(RevisionService)
-
-    container
-      .bind<SNSDomainEventPublisher>(TYPES.DomainEventPublisher)
-      .toConstantValue(new SNSDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN)))
-
-    const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
-      ['DUPLICATE_ITEM_SYNCED', container.get(TYPES.DuplicateItemSyncedEventHandler)],
-      ['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)],
-      ['EMAIL_BACKUP_REQUESTED', container.get(TYPES.EmailBackupRequestedEventHandler)],
-      ['CLOUD_BACKUP_REQUESTED', container.get(TYPES.CloudBackupRequestedEventHandler)],
-      [
-        'USER_CONTENT_SIZE_RECALCULATION_REQUESTED',
-        container.get(TYPES.UserContentSizeRecalculationRequestedEventHandler),
-      ],
-      ['ITEM_REVISION_CREATION_REQUESTED', container.get(TYPES.ItemRevisionCreationRequestedEventHandler)],
-    ])
-
-    container
-      .bind<DomainEventMessageHandlerInterface>(TYPES.DomainEventMessageHandler)
-      .toConstantValue(
-        env.get('NEW_RELIC_ENABLED', true) === 'true'
-          ? new SQSNewRelicEventMessageHandler(eventHandlers, container.get(TYPES.Logger))
-          : new SQSEventMessageHandler(eventHandlers, container.get(TYPES.Logger)),
-      )
-    container
-      .bind<DomainEventSubscriberFactoryInterface>(TYPES.DomainEventSubscriberFactory)
-      .toConstantValue(
-        new SQSDomainEventSubscriberFactory(
-          container.get(TYPES.SQS),
-          container.get(TYPES.SQS_QUEUE_URL),
-          container.get(TYPES.DomainEventMessageHandler),
-        ),
-      )
-
-    container.bind<ItemFactoryInterface>(TYPES.ItemFactory).to(ItemFactory)
-
-    container.bind<OwnershipFilter>(TYPES.OwnershipFilter).to(OwnershipFilter)
-    container.bind<TimeDifferenceFilter>(TYPES.TimeDifferenceFilter).to(TimeDifferenceFilter)
-    container.bind<UuidFilter>(TYPES.UuidFilter).to(UuidFilter)
-    container.bind<ContentTypeFilter>(TYPES.ContentTypeFilter).to(ContentTypeFilter)
-    container.bind<ContentFilter>(TYPES.ContentFilter).to(ContentFilter)
-
-    container
-      .bind<ItemSaveValidatorInterface>(TYPES.ItemSaveValidator)
-      .toConstantValue(
-        new ItemSaveValidator([
-          container.get(TYPES.OwnershipFilter),
-          container.get(TYPES.TimeDifferenceFilter),
-          container.get(TYPES.UuidFilter),
-          container.get(TYPES.ContentTypeFilter),
-          container.get(TYPES.ContentFilter),
-        ]),
-      )
-
-    return container
-  }
-}

+ 1 - 2
packages/syncing-server/src/Bootstrap/DataSource.ts

@@ -1,6 +1,5 @@
 import { DataSource, LoggerOptions } from 'typeorm'
 import { Item } from '../Domain/Item/Item'
-import { Revision } from '../Domain/Revision/Revision'
 import { Env } from './Env'
 
 const env: Env = new Env()
@@ -45,7 +44,7 @@ export const AppDataSource = new DataSource({
   username: inReplicaMode ? undefined : env.get('DB_USERNAME'),
   password: inReplicaMode ? undefined : env.get('DB_PASSWORD'),
   database: inReplicaMode ? undefined : env.get('DB_DATABASE'),
-  entities: [Item, Revision],
+  entities: [Item],
   migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
   migrationsRun: true,
   logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),

+ 0 - 2
packages/syncing-server/src/Bootstrap/Env.ts

@@ -1,7 +1,5 @@
 import { config, DotenvParseOutput } from 'dotenv'
-import { injectable } from 'inversify'
 
-@injectable()
 export class Env {
   private env?: DotenvParseOutput
 

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

@@ -0,0 +1,164 @@
+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 { ItemTransferCalculatorInterface } from '../Domain/Item/ItemTransferCalculatorInterface'
+import { ItemTransferCalculator } from '../Domain/Item/ItemTransferCalculator'
+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<ItemTransferCalculatorInterface>(TYPES.ItemTransferCalculator)
+      .toDynamicValue((context: interfaces.Context) => {
+        return new ItemTransferCalculator(
+          context.container.get(TYPES.ItemRepository),
+          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
+  }
+}

+ 1 - 7
packages/syncing-server/src/Bootstrap/Types.ts

@@ -5,16 +5,14 @@ const TYPES = {
   SNS: Symbol.for('SNS'),
   SQS: Symbol.for('SQS'),
   S3: Symbol.for('S3'),
+  Env: Symbol.for('Env'),
   // Repositories
-  RevisionRepository: Symbol.for('RevisionRepository'),
   ItemRepository: Symbol.for('ItemRepository'),
   // ORM
-  ORMRevisionRepository: Symbol.for('ORMRevisionRepository'),
   ORMItemRepository: Symbol.for('ORMItemRepository'),
   // Middleware
   AuthMiddleware: Symbol.for('AuthMiddleware'),
   // Projectors
-  RevisionProjector: Symbol.for('RevisionProjector'),
   ItemProjector: Symbol.for('ItemProjector'),
   SavedItemProjector: Symbol.for('SavedItemProjector'),
   ItemConflictProjector: Symbol.for('ItemConflictProjector'),
@@ -25,7 +23,6 @@ const TYPES = {
   SQS_QUEUE_URL: Symbol.for('SQS_QUEUE_URL'),
   SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
   AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
-  INTERNAL_DNS_REROUTE_ENABLED: Symbol.for('INTERNAL_DNS_REROUTE_ENABLED'),
   EXTENSIONS_SERVER_URL: Symbol.for('EXTENSIONS_SERVER_URL'),
   AUTH_SERVER_URL: Symbol.for('AUTH_SERVER_URL'),
   S3_AWS_REGION: Symbol.for('S3_AWS_REGION'),
@@ -48,8 +45,6 @@ const TYPES = {
   CloudBackupRequestedEventHandler: Symbol.for('CloudBackupRequestedEventHandler'),
   UserContentSizeRecalculationRequestedEventHandler: Symbol.for('UserContentSizeRecalculationRequestedEventHandler'),
   ItemRevisionCreationRequestedEventHandler: Symbol.for('ItemRevisionCreationRequestedEventHandler'),
-  // Map
-  RevisionMetadataMap: Symbol.for('RevisionMetadataMap'),
   // Services
   ContentDecoder: Symbol.for('ContentDecoder'),
   DomainEventPublisher: Symbol.for('DomainEventPublisher'),
@@ -65,7 +60,6 @@ const TYPES = {
   AuthHttpService: Symbol.for('AuthHttpService'),
   ExtensionsHttpService: Symbol.for('ExtensionsHttpService'),
   ItemBackupService: Symbol.for('ItemBackupService'),
-  RevisionService: Symbol.for('RevisionService'),
   ItemSaveValidator: Symbol.for('ItemSaveValidator'),
   OwnershipFilter: Symbol.for('OwnershipFilter'),
   TimeDifferenceFilter: Symbol.for('TimeDifferenceFilter'),

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

@@ -0,0 +1,225 @@
+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 { CloudBackupRequestedEventHandler } from '../Domain/Handler/CloudBackupRequestedEventHandler'
+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<CloudBackupRequestedEventHandler>(TYPES.CloudBackupRequestedEventHandler)
+      .toDynamicValue((context: interfaces.Context) => {
+        return new CloudBackupRequestedEventHandler(
+          context.container.get(TYPES.ItemRepository),
+          context.container.get(TYPES.AuthHttpService),
+          context.container.get(TYPES.ExtensionsHttpService),
+          context.container.get(TYPES.ItemBackupService),
+          context.container.get(TYPES.EXTENSIONS_SERVER_URL),
+          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)],
+          ['CLOUD_BACKUP_REQUESTED', context.container.get(TYPES.CloudBackupRequestedEventHandler)],
+          [
+            'USER_CONTENT_SIZE_RECALCULATION_REQUESTED',
+            context.container.get(TYPES.UserContentSizeRecalculationRequestedEventHandler),
+          ],
+          ['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 - 8
packages/syncing-server/src/Controller/AuthMiddleware.ts

@@ -1,19 +1,12 @@
 import { NextFunction, Request, Response } from 'express'
-import { inject, injectable } from 'inversify'
 import { BaseMiddleware } from 'inversify-express-utils'
 import { verify } from 'jsonwebtoken'
 import { CrossServiceTokenData } from '@standardnotes/security'
 import * as winston from 'winston'
 import { RoleName } from '@standardnotes/domain-core'
 
-import TYPES from '../Bootstrap/Types'
-
-@injectable()
 export class AuthMiddleware extends BaseMiddleware {
-  constructor(
-    @inject(TYPES.AUTH_JWT_SECRET) private authJWTSecret: string,
-    @inject(TYPES.Logger) private logger: winston.Logger,
-  ) {
+  constructor(private authJWTSecret: string, private logger: winston.Logger) {
     super()
   }
 

+ 0 - 99
packages/syncing-server/src/Controller/RevisionsController.spec.ts

@@ -1,99 +0,0 @@
-import 'reflect-metadata'
-
-import { Revision } from '../Domain/Revision/Revision'
-import * as express from 'express'
-
-import { RevisionsController } from './RevisionsController'
-import { results } from 'inversify-express-utils'
-import { ProjectorInterface } from '../Projection/ProjectorInterface'
-import { RevisionServiceInterface } from '../Domain/Revision/RevisionServiceInterface'
-import { RevisionProjection } from '../Projection/RevisionProjection'
-import { MapperInterface } from '@standardnotes/domain-core'
-import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
-import { SimpleRevisionProjection } from '../Projection/SimpleRevisionProjection'
-
-describe('RevisionsController', () => {
-  let revisionProjector: ProjectorInterface<Revision, RevisionProjection>
-  let revisionMap: MapperInterface<RevisionMetadata, SimpleRevisionProjection>
-  let revisionService: RevisionServiceInterface
-  let revision: Revision
-  let revisionMetadata: RevisionMetadata
-  let request: express.Request
-  let response: express.Response
-
-  const createController = () => new RevisionsController(revisionService, revisionProjector, revisionMap)
-
-  beforeEach(() => {
-    revision = {} as jest.Mocked<Revision>
-
-    revisionMetadata = {} as jest.Mocked<RevisionMetadata>
-
-    revisionMap = {} as jest.Mocked<MapperInterface<RevisionMetadata, SimpleRevisionProjection>>
-
-    revisionProjector = {} as jest.Mocked<ProjectorInterface<Revision, RevisionProjection>>
-
-    revisionService = {} as jest.Mocked<RevisionServiceInterface>
-    revisionService.getRevisionsMetadata = jest.fn().mockReturnValue([revisionMetadata])
-    revisionService.getRevision = jest.fn().mockReturnValue(revision)
-    revisionService.removeRevision = jest.fn().mockReturnValue(true)
-
-    request = {
-      params: {},
-    } as jest.Mocked<express.Request>
-
-    response = {
-      locals: {},
-    } as jest.Mocked<express.Response>
-    response.locals.user = {
-      uuid: '123',
-    }
-    response.locals.roleNames = ['BASIC_USER']
-  })
-
-  it('should return revisions for an item', async () => {
-    revisionMap.toProjection = jest.fn().mockReturnValue({ foo: 'bar' })
-
-    const revisionResponse = await createController().getRevisions(request, response)
-
-    expect(revisionResponse.json).toEqual([{ foo: 'bar' }])
-  })
-
-  it('should return a specific revision for an item', async () => {
-    revisionProjector.projectFull = jest.fn().mockReturnValue({ foo: 'bar' })
-
-    const httpResponse = <results.JsonResult>await createController().getRevision(request, response)
-
-    expect(httpResponse.json).toEqual({ foo: 'bar' })
-  })
-
-  it('should remove a specific revision for an item', async () => {
-    const httpResponse = await createController().deleteRevision(request, response)
-
-    expect(httpResponse).toBeInstanceOf(results.OkResult)
-  })
-
-  it('should not remove a specific revision for an item if it fails', async () => {
-    revisionService.removeRevision = jest.fn().mockReturnValue(false)
-
-    const httpResponse = await createController().deleteRevision(request, response)
-
-    expect(httpResponse).toBeInstanceOf(results.BadRequestResult)
-  })
-
-  it('should not remove a specific revision for an item the session is read only', async () => {
-    response.locals.readOnlyAccess = true
-
-    const httpResponse = await createController().deleteRevision(request, response)
-    const result = await httpResponse.executeAsync()
-
-    expect(result.statusCode).toEqual(401)
-  })
-
-  it('should return a 404 for a not found specific revision in an item', async () => {
-    revisionService.getRevision = jest.fn().mockReturnValue(null)
-
-    const httpResponse = await createController().getRevision(request, response)
-
-    expect(httpResponse).toBeInstanceOf(results.NotFoundResult)
-  })
-})

+ 0 - 85
packages/syncing-server/src/Controller/RevisionsController.ts

@@ -1,85 +0,0 @@
-import { Request, Response } from 'express'
-import { BaseHttpController, controller, httpDelete, httpGet, results } from 'inversify-express-utils'
-import { inject } from 'inversify'
-
-import TYPES from '../Bootstrap/Types'
-import { ProjectorInterface } from '../Projection/ProjectorInterface'
-import { Revision } from '../Domain/Revision/Revision'
-import { RevisionServiceInterface } from '../Domain/Revision/RevisionServiceInterface'
-import { ErrorTag } from '@standardnotes/api'
-import { RevisionProjection } from '../Projection/RevisionProjection'
-import { MapperInterface } from '@standardnotes/domain-core'
-import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
-import { SimpleRevisionProjection } from '../Projection/SimpleRevisionProjection'
-
-@controller('/items/:itemUuid/revisions', TYPES.AuthMiddleware)
-export class RevisionsController extends BaseHttpController {
-  constructor(
-    @inject(TYPES.RevisionService) private revisionService: RevisionServiceInterface,
-    @inject(TYPES.RevisionProjector) private revisionProjector: ProjectorInterface<Revision, RevisionProjection>,
-    @inject(TYPES.RevisionMetadataMap)
-    private revisionMetadataMap: MapperInterface<RevisionMetadata, SimpleRevisionProjection>,
-  ) {
-    super()
-  }
-
-  @httpGet('/')
-  public async getRevisions(req: Request, response: Response): Promise<results.JsonResult> {
-    const metadatas = await this.revisionService.getRevisionsMetadata(response.locals.user.uuid, req.params.itemUuid)
-
-    const metadataProjections = []
-    for (const metadata of metadatas) {
-      metadataProjections.push(this.revisionMetadataMap.toProjection(metadata))
-    }
-
-    return this.json(metadataProjections)
-  }
-
-  @httpGet('/:uuid')
-  public async getRevision(request: Request, response: Response): Promise<results.JsonResult | results.NotFoundResult> {
-    const revision = await this.revisionService.getRevision({
-      userRoles: response.locals.roleNames,
-      userUuid: response.locals.user.uuid,
-      itemUuid: request.params.itemUuid,
-      revisionUuid: request.params.uuid,
-    })
-
-    if (!revision) {
-      return this.notFound()
-    }
-
-    const revisionProjection = await this.revisionProjector.projectFull(revision)
-
-    return this.json(revisionProjection)
-  }
-
-  @httpDelete('/:uuid')
-  public async deleteRevision(
-    request: Request,
-    response: Response,
-  ): Promise<results.BadRequestResult | results.OkResult | results.JsonResult> {
-    if (response.locals.readOnlyAccess) {
-      return this.json(
-        {
-          error: {
-            tag: ErrorTag.ReadOnlyAccess,
-            message: 'Session has read-only access.',
-          },
-        },
-        401,
-      )
-    }
-
-    const success = await this.revisionService.removeRevision({
-      userUuid: response.locals.user.uuid,
-      itemUuid: request.params.itemUuid,
-      revisionUuid: request.params.uuid,
-    })
-
-    if (!success) {
-      return this.badRequest()
-    }
-
-    return this.ok()
-  }
-}

+ 1 - 23
packages/syncing-server/src/Domain/Event/DomainEventFactory.ts

@@ -6,35 +6,13 @@ import {
   ItemDumpedEvent,
   ItemRevisionCreationRequestedEvent,
   RevisionsCopyRequestedEvent,
-  RevisionsOwnershipUpdateRequestedEvent,
   UserContentSizeRecalculationRequestedEvent,
 } from '@standardnotes/domain-events'
 import { TimerInterface } from '@standardnotes/time'
-import { inject, injectable } from 'inversify'
-import TYPES from '../../Bootstrap/Types'
 import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
 
-@injectable()
 export class DomainEventFactory implements DomainEventFactoryInterface {
-  constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
-
-  createRevisionsOwnershipUpdateRequestedEvent(dto: {
-    userUuid: string
-    itemUuid: string
-  }): RevisionsOwnershipUpdateRequestedEvent {
-    return {
-      type: 'REVISIONS_OWNERSHIP_UPDATE_REQUESTED',
-      createdAt: this.timer.getUTCDate(),
-      meta: {
-        correlation: {
-          userIdentifier: dto.userUuid,
-          userIdentifierType: 'uuid',
-        },
-        origin: DomainEventService.SyncingServer,
-      },
-      payload: dto,
-    }
-  }
+  constructor(private timer: TimerInterface) {}
 
   createRevisionsCopyRequestedEvent(
     userUuid: string,

+ 0 - 5
packages/syncing-server/src/Domain/Event/DomainEventFactoryInterface.ts

@@ -4,7 +4,6 @@ import {
   ItemDumpedEvent,
   ItemRevisionCreationRequestedEvent,
   RevisionsCopyRequestedEvent,
-  RevisionsOwnershipUpdateRequestedEvent,
   UserContentSizeRecalculationRequestedEvent,
 } from '@standardnotes/domain-events'
 
@@ -31,8 +30,4 @@ export interface DomainEventFactoryInterface {
     userUuid: string,
     dto: { originalItemUuid: string; newItemUuid: string },
   ): RevisionsCopyRequestedEvent
-  createRevisionsOwnershipUpdateRequestedEvent(dto: {
-    userUuid: string
-    itemUuid: string
-  }): RevisionsOwnershipUpdateRequestedEvent
 }

+ 7 - 9
packages/syncing-server/src/Domain/Extension/ExtensionsHttpService.ts

@@ -2,9 +2,8 @@ import { KeyParamsData } from '@standardnotes/responses'
 import { DomainEventInterface, DomainEventPublisherInterface } from '@standardnotes/domain-events'
 import { EmailLevel } from '@standardnotes/domain-core'
 import { AxiosInstance } from 'axios'
-import { inject, injectable } from 'inversify'
 import { Logger } from 'winston'
-import TYPES from '../../Bootstrap/Types'
+
 import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
 import { ContentDecoderInterface } from '../Item/ContentDecoderInterface'
 import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
@@ -15,15 +14,14 @@ import { getBody as googleDriveBody, getSubject as googleDriveSubject } from '..
 import { getBody as dropboxBody, getSubject as dropboxSubject } from '../Email/DropboxBackupFailed'
 import { getBody as oneDriveBody, getSubject as oneDriveSubject } from '../Email/OneDriveBackupFailed'
 
-@injectable()
 export class ExtensionsHttpService implements ExtensionsHttpServiceInterface {
   constructor(
-    @inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
-    @inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
-    @inject(TYPES.ContentDecoder) private contentDecoder: ContentDecoderInterface,
-    @inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
-    @inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
-    @inject(TYPES.Logger) private logger: Logger,
+    private httpClient: AxiosInstance,
+    private itemRepository: ItemRepositoryInterface,
+    private contentDecoder: ContentDecoderInterface,
+    private domainEventPublisher: DomainEventPublisherInterface,
+    private domainEventFactory: DomainEventFactoryInterface,
+    private logger: Logger,
   ) {}
 
   async triggerCloudBackupOnExtensionsServer(dto: {

+ 1 - 1
packages/syncing-server/src/Domain/Handler/AccountDeletionRequestedEventHandler.spec.ts

@@ -36,7 +36,7 @@ describe('AccountDeletionRequestedEventHandler', () => {
     }
   })
 
-  it('should remove all items and revision for a user', async () => {
+  it('should remove all items for a user', async () => {
     await createHandler().handle(event)
 
     expect(itemRepository.deleteByUserUuid).toHaveBeenCalledWith('2-3-4')

+ 1 - 7
packages/syncing-server/src/Domain/Handler/AccountDeletionRequestedEventHandler.ts

@@ -1,15 +1,9 @@
 import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
-import { inject, injectable } from 'inversify'
 import { Logger } from 'winston'
-import TYPES from '../../Bootstrap/Types'
 import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
 
-@injectable()
 export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
-  constructor(
-    @inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
-    @inject(TYPES.Logger) private logger: Logger,
-  ) {}
+  constructor(private itemRepository: ItemRepositoryInterface, private logger: Logger) {}
 
   async handle(event: AccountDeletionRequestedEvent): Promise<void> {
     await this.itemRepository.deleteByUserUuid(event.payload.userUuid)

+ 6 - 9
packages/syncing-server/src/Domain/Handler/CloudBackupRequestedEventHandler.ts

@@ -1,7 +1,5 @@
 import { DomainEventHandlerInterface, CloudBackupRequestedEvent } from '@standardnotes/domain-events'
-import { inject, injectable } from 'inversify'
 
-import TYPES from '../../Bootstrap/Types'
 import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
 import { ItemQuery } from '../Item/ItemQuery'
 import { AuthHttpServiceInterface } from '../Auth/AuthHttpServiceInterface'
@@ -11,15 +9,14 @@ import { ItemBackupServiceInterface } from '../Item/ItemBackupServiceInterface'
 import { Logger } from 'winston'
 import { KeyParamsData } from '@standardnotes/responses'
 
-@injectable()
 export class CloudBackupRequestedEventHandler implements DomainEventHandlerInterface {
   constructor(
-    @inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
-    @inject(TYPES.AuthHttpService) private authHttpService: AuthHttpServiceInterface,
-    @inject(TYPES.ExtensionsHttpService) private extensionsHttpService: ExtensionsHttpServiceInterface,
-    @inject(TYPES.ItemBackupService) private itemBackupService: ItemBackupServiceInterface,
-    @inject(TYPES.EXTENSIONS_SERVER_URL) private extensionsServerUrl: string,
-    @inject(TYPES.Logger) private logger: Logger,
+    private itemRepository: ItemRepositoryInterface,
+    private authHttpService: AuthHttpServiceInterface,
+    private extensionsHttpService: ExtensionsHttpServiceInterface,
+    private itemBackupService: ItemBackupServiceInterface,
+    private extensionsServerUrl: string,
+    private logger: Logger,
   ) {}
 
   async handle(event: CloudBackupRequestedEvent): Promise<void> {

+ 4 - 7
packages/syncing-server/src/Domain/Handler/DuplicateItemSyncedEventHandler.ts

@@ -3,19 +3,16 @@ import {
   DomainEventPublisherInterface,
   DuplicateItemSyncedEvent,
 } from '@standardnotes/domain-events'
-import { inject, injectable } from 'inversify'
 import { Logger } from 'winston'
-import TYPES from '../../Bootstrap/Types'
 import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
 import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
 
-@injectable()
 export class DuplicateItemSyncedEventHandler implements DomainEventHandlerInterface {
   constructor(
-    @inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
-    @inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
-    @inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
-    @inject(TYPES.Logger) private logger: Logger,
+    private itemRepository: ItemRepositoryInterface,
+    private domainEventFactory: DomainEventFactoryInterface,
+    private domainEventPublisher: DomainEventPublisherInterface,
+    private logger: Logger,
   ) {}
 
   async handle(event: DuplicateItemSyncedEvent): Promise<void> {

+ 9 - 12
packages/syncing-server/src/Domain/Handler/EmailBackupRequestedEventHandler.ts

@@ -5,9 +5,7 @@ import {
   EmailBackupRequestedEvent,
 } from '@standardnotes/domain-events'
 import { EmailLevel } from '@standardnotes/domain-core'
-import { inject, injectable } from 'inversify'
 import { Logger } from 'winston'
-import TYPES from '../../Bootstrap/Types'
 import { AuthHttpServiceInterface } from '../Auth/AuthHttpServiceInterface'
 import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
 import { ItemBackupServiceInterface } from '../Item/ItemBackupServiceInterface'
@@ -16,18 +14,17 @@ import { ItemTransferCalculatorInterface } from '../Item/ItemTransferCalculatorI
 import { ItemQuery } from '../Item/ItemQuery'
 import { getBody, getSubject } from '../Email/EmailBackupAttachmentCreated'
 
-@injectable()
 export class EmailBackupRequestedEventHandler implements DomainEventHandlerInterface {
   constructor(
-    @inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
-    @inject(TYPES.AuthHttpService) private authHttpService: AuthHttpServiceInterface,
-    @inject(TYPES.ItemBackupService) private itemBackupService: ItemBackupServiceInterface,
-    @inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
-    @inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
-    @inject(TYPES.EMAIL_ATTACHMENT_MAX_BYTE_SIZE) private emailAttachmentMaxByteSize: number,
-    @inject(TYPES.ItemTransferCalculator) private itemTransferCalculator: ItemTransferCalculatorInterface,
-    @inject(TYPES.S3_BACKUP_BUCKET_NAME) private s3BackupBucketName: string,
-    @inject(TYPES.Logger) private logger: Logger,
+    private itemRepository: ItemRepositoryInterface,
+    private authHttpService: AuthHttpServiceInterface,
+    private itemBackupService: ItemBackupServiceInterface,
+    private domainEventPublisher: DomainEventPublisherInterface,
+    private domainEventFactory: DomainEventFactoryInterface,
+    private emailAttachmentMaxByteSize: number,
+    private itemTransferCalculator: ItemTransferCalculatorInterface,
+    private s3BackupBucketName: string,
+    private logger: Logger,
   ) {}
 
   async handle(event: EmailBackupRequestedEvent): Promise<void> {

+ 4 - 7
packages/syncing-server/src/Domain/Handler/ItemRevisionCreationRequestedEventHandler.ts

@@ -3,20 +3,17 @@ import {
   DomainEventHandlerInterface,
   DomainEventPublisherInterface,
 } from '@standardnotes/domain-events'
-import { inject, injectable } from 'inversify'
 
-import TYPES from '../../Bootstrap/Types'
 import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
 import { ItemBackupServiceInterface } from '../Item/ItemBackupServiceInterface'
 import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
 
-@injectable()
 export class ItemRevisionCreationRequestedEventHandler implements DomainEventHandlerInterface {
   constructor(
-    @inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
-    @inject(TYPES.ItemBackupService) private itemBackupService: ItemBackupServiceInterface,
-    @inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
-    @inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
+    private itemRepository: ItemRepositoryInterface,
+    private itemBackupService: ItemBackupServiceInterface,
+    private domainEventFactory: DomainEventFactoryInterface,
+    private domainEventPublisher: DomainEventPublisherInterface,
   ) {}
 
   async handle(event: ItemRevisionCreationRequestedEvent): Promise<void> {

+ 0 - 73
packages/syncing-server/src/Domain/Handler/UserContentSizeRecalculationRequestedEventHandler.ts

@@ -1,73 +0,0 @@
-/* istanbul ignore file */
-
-import { DomainEventHandlerInterface, UserContentSizeRecalculationRequestedEvent } from '@standardnotes/domain-events'
-import { inject, injectable } from 'inversify'
-import { Stream } from 'stream'
-import { Logger } from 'winston'
-import TYPES from '../../Bootstrap/Types'
-import { ItemProjection } from '../../Projection/ItemProjection'
-import { ProjectorInterface } from '../../Projection/ProjectorInterface'
-import { Item } from '../Item/Item'
-import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
-
-@injectable()
-export class UserContentSizeRecalculationRequestedEventHandler implements DomainEventHandlerInterface {
-  constructor(
-    @inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
-    @inject(TYPES.ItemProjector) private itemProjector: ProjectorInterface<Item, ItemProjection>,
-    @inject(TYPES.Logger) private logger: Logger,
-  ) {}
-
-  async handle(event: UserContentSizeRecalculationRequestedEvent): Promise<void> {
-    this.logger.debug(`Starting content size recalculation for user: ${event.payload.userUuid}`)
-
-    const stream = await this.itemRepository.streamAll({
-      deleted: false,
-      userUuid: event.payload.userUuid,
-      sortBy: 'updated_at_timestamp',
-      sortOrder: 'ASC',
-    })
-
-    const loggerHandle = this.logger
-
-    await new Promise((resolve, reject) => {
-      stream
-        .pipe(
-          new Stream.Transform({
-            objectMode: true,
-            transform: async (item, _encoding, callback) => {
-              if (!item.item_uuid) {
-                callback()
-
-                return
-              }
-              loggerHandle.debug(`Fixing content size for item ${item.item_uuid}`)
-
-              const modelItem = await this.itemRepository.findByUuid(item.item_uuid)
-              if (modelItem !== null) {
-                const fixedContentSize = Buffer.byteLength(
-                  JSON.stringify(await this.itemProjector.projectFull(modelItem)),
-                )
-                if (modelItem.contentSize !== fixedContentSize) {
-                  loggerHandle.debug(`Fixing content size from ${modelItem.contentSize} to ${fixedContentSize}`)
-
-                  modelItem.contentSize = fixedContentSize
-
-                  await this.itemRepository.save(modelItem)
-                }
-                callback()
-
-                return
-              }
-
-              callback()
-
-              return
-            },
-          }),
-        )
-        .on('finish', resolve)
-        .on('error', reject)
-    })
-  }
-}

+ 0 - 2
packages/syncing-server/src/Domain/Item/ContentDecoder.ts

@@ -1,7 +1,5 @@
-import { injectable } from 'inversify'
 import { ContentDecoderInterface } from './ContentDecoderInterface'
 
-@injectable()
 export class ContentDecoder implements ContentDecoderInterface {
   decode(content: string): Record<string, unknown> {
     try {

+ 1 - 10
packages/syncing-server/src/Domain/Item/Item.ts

@@ -1,6 +1,5 @@
 import { ContentType } from '@standardnotes/common'
-import { Column, Entity, Index, OneToMany, PrimaryGeneratedColumn } from 'typeorm'
-import { Revision } from '../Revision/Revision'
+import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
 
 @Entity({ name: 'items' })
 @Index('index_items_on_user_uuid_and_content_type', ['userUuid', 'contentType'])
@@ -110,14 +109,6 @@ export class Item {
   @Index('updated_at_timestamp')
   declare updatedAtTimestamp: number
 
-  @OneToMany(
-    /* istanbul ignore next */
-    () => Revision,
-    /* istanbul ignore next */
-    (revision) => revision.item,
-  )
-  declare revisions: Promise<Revision[]>
-
   @Column({
     name: 'updated_with_session',
     type: 'varchar',

+ 1 - 7
packages/syncing-server/src/Domain/Item/ItemFactory.ts

@@ -1,19 +1,13 @@
 import { TimerInterface } from '@standardnotes/time'
-import { inject, injectable } from 'inversify'
 
-import TYPES from '../../Bootstrap/Types'
 import { ItemProjection } from '../../Projection/ItemProjection'
 import { ProjectorInterface } from '../../Projection/ProjectorInterface'
 import { Item } from './Item'
 import { ItemFactoryInterface } from './ItemFactoryInterface'
 import { ItemHash } from './ItemHash'
 
-@injectable()
 export class ItemFactory implements ItemFactoryInterface {
-  constructor(
-    @inject(TYPES.Timer) private timer: TimerInterface,
-    @inject(TYPES.ItemProjector) private itemProjector: ProjectorInterface<Item, ItemProjection>,
-  ) {}
+  constructor(private timer: TimerInterface, private itemProjector: ProjectorInterface<Item, ItemProjection>) {}
 
   createStub(dto: { userUuid: string; itemHash: ItemHash; sessionUuid: string | null }): Item {
     const item = this.create(dto)

+ 12 - 15
packages/syncing-server/src/Domain/Item/ItemService.ts

@@ -1,10 +1,8 @@
 import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
 import { Time, TimerInterface } from '@standardnotes/time'
 import { ContentType } from '@standardnotes/common'
-import { inject, injectable } from 'inversify'
 import { Logger } from 'winston'
 
-import TYPES from '../../Bootstrap/Types'
 import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
 import { GetItemsDTO } from './GetItemsDTO'
 import { GetItemsResult } from './GetItemsResult'
@@ -23,24 +21,23 @@ import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterfa
 import { ProjectorInterface } from '../../Projection/ProjectorInterface'
 import { ItemProjection } from '../../Projection/ItemProjection'
 
-@injectable()
 export class ItemService implements ItemServiceInterface {
   private readonly DEFAULT_ITEMS_LIMIT = 150
   private readonly SYNC_TOKEN_VERSION = 2
 
   constructor(
-    @inject(TYPES.ItemSaveValidator) private itemSaveValidator: ItemSaveValidatorInterface,
-    @inject(TYPES.ItemFactory) private itemFactory: ItemFactoryInterface,
-    @inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
-    @inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
-    @inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
-    @inject(TYPES.REVISIONS_FREQUENCY) private revisionFrequency: number,
-    @inject(TYPES.CONTENT_SIZE_TRANSFER_LIMIT) private contentSizeTransferLimit: number,
-    @inject(TYPES.ItemTransferCalculator) private itemTransferCalculator: ItemTransferCalculatorInterface,
-    @inject(TYPES.Timer) private timer: TimerInterface,
-    @inject(TYPES.ItemProjector) private itemProjector: ProjectorInterface<Item, ItemProjection>,
-    @inject(TYPES.MAX_ITEMS_LIMIT) private maxItemsSyncLimit: number,
-    @inject(TYPES.Logger) private logger: Logger,
+    private itemSaveValidator: ItemSaveValidatorInterface,
+    private itemFactory: ItemFactoryInterface,
+    private itemRepository: ItemRepositoryInterface,
+    private domainEventPublisher: DomainEventPublisherInterface,
+    private domainEventFactory: DomainEventFactoryInterface,
+    private revisionFrequency: number,
+    private contentSizeTransferLimit: number,
+    private itemTransferCalculator: ItemTransferCalculatorInterface,
+    private timer: TimerInterface,
+    private itemProjector: ProjectorInterface<Item, ItemProjection>,
+    private maxItemsSyncLimit: number,
+    private logger: Logger,
   ) {}
 
   async getItems(dto: GetItemsDTO): Promise<GetItemsResult> {

+ 1 - 8
packages/syncing-server/src/Domain/Item/ItemTransferCalculator.ts

@@ -1,18 +1,11 @@
-import { inject, injectable } from 'inversify'
 import { Logger } from 'winston'
 
-import TYPES from '../../Bootstrap/Types'
-
 import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
 import { ItemQuery } from './ItemQuery'
 import { ItemRepositoryInterface } from './ItemRepositoryInterface'
 
-@injectable()
 export class ItemTransferCalculator implements ItemTransferCalculatorInterface {
-  constructor(
-    @inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
-    @inject(TYPES.Logger) private logger: Logger,
-  ) {}
+  constructor(private itemRepository: ItemRepositoryInterface, private logger: Logger) {}
 
   async computeItemUuidsToFetch(itemQuery: ItemQuery, bytesTransferLimit: number): Promise<Array<string>> {
     const itemUuidsToFetch = []

+ 0 - 2
packages/syncing-server/src/Domain/Item/SaveRule/ContentFilter.ts

@@ -1,10 +1,8 @@
-import { injectable } from 'inversify'
 import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
 import { ItemSaveRuleResult } from './ItemSaveRuleResult'
 import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
 import { ConflictType } from '@standardnotes/responses'
 
-@injectable()
 export class ContentFilter implements ItemSaveRuleInterface {
   async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
     if (dto.itemHash.content === undefined || dto.itemHash.content === null) {

+ 1 - 2
packages/syncing-server/src/Domain/Item/SaveRule/ContentTypeFilter.ts

@@ -1,11 +1,10 @@
 import { ContentType } from '@standardnotes/common'
-import { injectable } from 'inversify'
+
 import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
 import { ItemSaveRuleResult } from './ItemSaveRuleResult'
 import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
 import { ConflictType } from '@standardnotes/responses'
 
-@injectable()
 export class ContentTypeFilter implements ItemSaveRuleInterface {
   async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
     const validContentType = Object.values(ContentType).includes(dto.itemHash.content_type as ContentType)

+ 0 - 2
packages/syncing-server/src/Domain/Item/SaveRule/OwnershipFilter.ts

@@ -1,10 +1,8 @@
-import { injectable } from 'inversify'
 import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
 import { ItemSaveRuleResult } from './ItemSaveRuleResult'
 import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
 import { ConflictType } from '@standardnotes/responses'
 
-@injectable()
 export class OwnershipFilter implements ItemSaveRuleInterface {
   async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
     const itemBelongsToADifferentUser = dto.existingItem !== null && dto.existingItem.userUuid !== dto.userUuid

+ 2 - 4
packages/syncing-server/src/Domain/Item/SaveRule/TimeDifferenceFilter.ts

@@ -1,6 +1,5 @@
 import { Time, TimerInterface } from '@standardnotes/time'
-import { inject, injectable } from 'inversify'
-import TYPES from '../../../Bootstrap/Types'
+
 import { ApiVersion } from '../../Api/ApiVersion'
 import { ItemHash } from '../ItemHash'
 import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
@@ -8,9 +7,8 @@ import { ItemSaveRuleResult } from './ItemSaveRuleResult'
 import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
 import { ConflictType } from '@standardnotes/responses'
 
-@injectable()
 export class TimeDifferenceFilter implements ItemSaveRuleInterface {
-  constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
+  constructor(private timer: TimerInterface) {}
 
   async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
     if (!dto.existingItem) {

+ 0 - 2
packages/syncing-server/src/Domain/Item/SaveRule/UuidFilter.ts

@@ -1,11 +1,9 @@
-import { injectable } from 'inversify'
 import { validate } from 'uuid'
 import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
 import { ItemSaveRuleResult } from './ItemSaveRuleResult'
 import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
 import { ConflictType } from '@standardnotes/responses'
 
-@injectable()
 export class UuidFilter implements ItemSaveRuleInterface {
   async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
     const validUuid = validate(dto.itemHash.uuid)

+ 0 - 2
packages/syncing-server/src/Domain/Item/SaveValidator/ItemSaveValidator.ts

@@ -1,10 +1,8 @@
-import { injectable } from 'inversify'
 import { ItemSaveRuleInterface } from '../SaveRule/ItemSaveRuleInterface'
 import { ItemSaveValidationDTO } from './ItemSaveValidationDTO'
 import { ItemSaveValidationResult } from './ItemSaveValidationResult'
 import { ItemSaveValidatorInterface } from './ItemSaveValidatorInterface'
 
-@injectable()
 export class ItemSaveValidator implements ItemSaveValidatorInterface {
   constructor(private rules: Array<ItemSaveRuleInterface>) {}
 

+ 1 - 4
packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20161215.ts

@@ -1,5 +1,3 @@
-import { inject, injectable } from 'inversify'
-import TYPES from '../../../Bootstrap/Types'
 import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
 import { SyncItemsResponse } from '../../UseCase/SyncItemsResponse'
 import { Item } from '../Item'
@@ -10,11 +8,10 @@ import { SyncResponse20161215 } from './SyncResponse20161215'
 import { SyncResponseFactoryInterface } from './SyncResponseFactoryInterface'
 import { ConflictType } from '@standardnotes/responses'
 
-@injectable()
 export class SyncResponseFactory20161215 implements SyncResponseFactoryInterface {
   private readonly LEGACY_MIN_CONFLICT_INTERVAL = 20_000_000
 
-  constructor(@inject(TYPES.ItemProjector) private itemProjector: ProjectorInterface<Item, ItemProjection>) {}
+  constructor(private itemProjector: ProjectorInterface<Item, ItemProjection>) {}
 
   async createResponse(syncItemsResponse: SyncItemsResponse): Promise<SyncResponse20161215> {
     const conflicts = syncItemsResponse.conflicts.filter(

+ 2 - 6
packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20200115.ts

@@ -1,5 +1,3 @@
-import { inject, injectable } from 'inversify'
-import TYPES from '../../../Bootstrap/Types'
 import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
 import { SyncItemsResponse } from '../../UseCase/SyncItemsResponse'
 import { Item } from '../Item'
@@ -10,13 +8,11 @@ import { SyncResponse20200115 } from './SyncResponse20200115'
 import { SyncResponseFactoryInterface } from './SyncResponseFactoryInterface'
 import { SavedItemProjection } from '../../../Projection/SavedItemProjection'
 
-@injectable()
 export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface {
   constructor(
-    @inject(TYPES.ItemProjector) private itemProjector: ProjectorInterface<Item, ItemProjection>,
-    @inject(TYPES.ItemConflictProjector)
+    private itemProjector: ProjectorInterface<Item, ItemProjection>,
     private itemConflictProjector: ProjectorInterface<ItemConflict, ItemConflictProjection>,
-    @inject(TYPES.SavedItemProjector) private savedItemProjector: ProjectorInterface<Item, SavedItemProjection>,
+    private savedItemProjector: ProjectorInterface<Item, SavedItemProjection>,
   ) {}
 
   async createResponse(syncItemsResponse: SyncItemsResponse): Promise<SyncResponse20200115> {

+ 2 - 5
packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactoryResolver.ts

@@ -1,16 +1,13 @@
-import { inject, injectable } from 'inversify'
-import TYPES from '../../../Bootstrap/Types'
 import { ApiVersion } from '../../Api/ApiVersion'
 import { SyncResponseFactory20161215 } from './SyncResponseFactory20161215'
 import { SyncResponseFactory20200115 } from './SyncResponseFactory20200115'
 import { SyncResponseFactoryInterface } from './SyncResponseFactoryInterface'
 import { SyncResponseFactoryResolverInterface } from './SyncResponseFactoryResolverInterface'
 
-@injectable()
 export class SyncResponseFactoryResolver implements SyncResponseFactoryResolverInterface {
   constructor(
-    @inject(TYPES.SyncResponseFactory20161215) private syncResponseFactory20161215: SyncResponseFactory20161215,
-    @inject(TYPES.SyncResponseFactory20200115) private syncResponseFactory20200115: SyncResponseFactory20200115,
+    private syncResponseFactory20161215: SyncResponseFactory20161215,
+    private syncResponseFactory20200115: SyncResponseFactory20200115,
   ) {}
 
   resolveSyncResponseFactoryVersion(apiVersion?: string): SyncResponseFactoryInterface {

+ 0 - 44
packages/syncing-server/src/Domain/Map/RevisionMetadataMap.ts

@@ -1,44 +0,0 @@
-/* istanbul ignore file */
-import { ContentType } from '@standardnotes/common'
-import { MapperInterface, UniqueEntityId } from '@standardnotes/domain-core'
-import { TimerInterface } from '@standardnotes/time'
-import { inject, injectable } from 'inversify'
-
-import TYPES from '../../Bootstrap/Types'
-import { SimpleRevisionProjection } from '../../Projection/SimpleRevisionProjection'
-import { RevisionMetadata } from '../Revision/RevisionMetadata'
-import { RevisionServiceInterface } from '../Revision/RevisionServiceInterface'
-
-@injectable()
-export class RevisionMetadataMap implements MapperInterface<RevisionMetadata, SimpleRevisionProjection> {
-  constructor(
-    @inject(TYPES.RevisionService) private revisionService: RevisionServiceInterface,
-    @inject(TYPES.Timer) private timer: TimerInterface,
-  ) {}
-
-  toDomain(persistence: SimpleRevisionProjection): RevisionMetadata {
-    const revisionMetadatOrError = RevisionMetadata.create(
-      {
-        contentType: persistence.content_type,
-        createdAt: this.timer.convertStringDateToDate(persistence.created_at),
-        updatedAt: this.timer.convertStringDateToDate(persistence.updated_at),
-      },
-      new UniqueEntityId(persistence.uuid),
-    )
-    if (revisionMetadatOrError.isFailed()) {
-      throw new Error(revisionMetadatOrError.getError())
-    }
-
-    return revisionMetadatOrError.getValue()
-  }
-
-  toProjection(domain: RevisionMetadata): SimpleRevisionProjection {
-    return {
-      uuid: domain.id.toString(),
-      content_type: domain.props.contentType as ContentType | null,
-      required_role: this.revisionService.calculateRequiredRoleBasedOnRevisionDate(domain.props.createdAt),
-      created_at: this.timer.convertDateToISOString(domain.props.createdAt),
-      updated_at: this.timer.convertDateToISOString(domain.props.updatedAt),
-    }
-  }
-}

+ 0 - 7
packages/syncing-server/src/Domain/Revision/Revision.spec.ts

@@ -1,7 +0,0 @@
-import { Revision } from './Revision'
-
-describe('Revision', () => {
-  it('should instantiate', () => {
-    expect(new Revision()).toBeInstanceOf(Revision)
-  })
-})

+ 0 - 81
packages/syncing-server/src/Domain/Revision/Revision.ts

@@ -1,81 +0,0 @@
-import { ContentType } from '@standardnotes/common'
-import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'
-import { Item } from '../Item/Item'
-
-@Entity({ name: 'revisions' })
-export class Revision {
-  @PrimaryGeneratedColumn('uuid')
-  declare uuid: string
-
-  @ManyToOne(
-    /* istanbul ignore next */
-    () => Item,
-    /* istanbul ignore next */
-    (item) => item.revisions,
-    { onDelete: 'CASCADE' },
-  )
-  @JoinColumn({ name: 'item_uuid', referencedColumnName: 'uuid' })
-  declare item: Promise<Item>
-
-  @Column({
-    type: 'mediumtext',
-    nullable: true,
-  })
-  declare content: string | null
-
-  @Column({
-    name: 'content_type',
-    type: 'varchar',
-    length: 255,
-    nullable: true,
-  })
-  declare contentType: ContentType | null
-
-  @Column({
-    type: 'varchar',
-    name: 'items_key_id',
-    length: 255,
-    nullable: true,
-  })
-  declare itemsKeyId: string | null
-
-  @Column({
-    name: 'enc_item_key',
-    type: 'text',
-    nullable: true,
-  })
-  declare encItemKey: string | null
-
-  @Column({
-    name: 'auth_hash',
-    type: 'varchar',
-    length: 255,
-    nullable: true,
-  })
-  declare authHash: string | null
-
-  @Column({
-    name: 'creation_date',
-    type: 'date',
-    nullable: true,
-  })
-  @Index('index_revisions_on_creation_date')
-  declare creationDate: Date
-
-  @Column({
-    name: 'created_at',
-    type: 'datetime',
-    precision: 6,
-    nullable: true,
-  })
-  @Index('index_revisions_on_created_at')
-  declare createdAt: Date
-
-  @Column({
-    name: 'updated_at',
-    type: 'datetime',
-    precision: 6,
-    nullable: true,
-  })
-  declare updatedAt: Date
-}

+ 0 - 28
packages/syncing-server/src/Domain/Revision/RevisionMetadata.ts

@@ -1,28 +0,0 @@
-import { UniqueEntityId, Entity, Result } from '@standardnotes/domain-core'
-
-import { RevisionMetadataProps } from './RevisionMetadataProps'
-
-export class RevisionMetadata extends Entity<RevisionMetadataProps> {
-  get id(): UniqueEntityId {
-    return this._id
-  }
-
-  private constructor(props: RevisionMetadataProps, id?: UniqueEntityId) {
-    super(props, id)
-  }
-
-  static create(props: RevisionMetadataProps, id?: UniqueEntityId): Result<RevisionMetadata> {
-    if (!(props.createdAt instanceof Date)) {
-      return Result.fail<RevisionMetadata>(
-        `Could not create Revision Metadata. Creation date should be a date object, given: ${props.createdAt}`,
-      )
-    }
-    if (!(props.updatedAt instanceof Date)) {
-      return Result.fail<RevisionMetadata>(
-        `Could not create Revision Metadata. Update date should be a date object, given: ${props.updatedAt}`,
-      )
-    }
-
-    return Result.ok<RevisionMetadata>(new RevisionMetadata(props, id))
-  }
-}

+ 0 - 5
packages/syncing-server/src/Domain/Revision/RevisionMetadataProps.ts

@@ -1,5 +0,0 @@
-export interface RevisionMetadataProps {
-  contentType: string | null
-  createdAt: Date
-  updatedAt: Date
-}

+ 0 - 10
packages/syncing-server/src/Domain/Revision/RevisionRepositoryInterface.ts

@@ -1,10 +0,0 @@
-import { Revision } from './Revision'
-import { RevisionMetadata } from './RevisionMetadata'
-
-export interface RevisionRepositoryInterface {
-  findByItemId(parameters: { itemUuid: string; afterDate?: Date }): Promise<Array<Revision>>
-  findOneById(itemId: string, id: string): Promise<Revision | null>
-  save(revision: Revision): Promise<Revision>
-  removeByUuid(itemUuid: string, revisionUuid: string): Promise<void>
-  findMetadataByItemId(itemUuid: string): Promise<Array<RevisionMetadata>>
-}

+ 0 - 193
packages/syncing-server/src/Domain/Revision/RevisionService.spec.ts

@@ -1,193 +0,0 @@
-import 'reflect-metadata'
-
-import { RoleName } from '@standardnotes/domain-core'
-import { TimerInterface } from '@standardnotes/time'
-
-import { Item } from '../Item/Item'
-import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
-import { Revision } from './Revision'
-import { RevisionMetadata } from './RevisionMetadata'
-import { RevisionRepositoryInterface } from './RevisionRepositoryInterface'
-import { RevisionService } from './RevisionService'
-
-describe('RevisionService', () => {
-  let revisionRepository: RevisionRepositoryInterface
-  let timer: TimerInterface
-  let itemRepository: ItemRepositoryInterface
-  let revision1: Revision
-  let revision2: Revision
-
-  const createService = () => new RevisionService(revisionRepository, itemRepository, timer)
-
-  beforeEach(() => {
-    revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
-    revisionRepository.save = jest.fn().mockImplementation((revision: Revision) => {
-      revision.uuid = '3-4-5'
-
-      return revision
-    })
-
-    timer = {} as jest.Mocked<TimerInterface>
-    timer.dateWasNDaysAgo = jest.fn().mockReturnValue(0)
-
-    itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
-    itemRepository.findByUuid = jest.fn().mockReturnValue({
-      userUuid: '1-2-3',
-    } as jest.Mocked<Item>)
-
-    revision1 = {
-      uuid: '1-2-3',
-      item: Promise.resolve({
-        uuid: '1-2-3',
-      }),
-      content: 'content1',
-    } as jest.Mocked<Revision>
-
-    revision2 = {
-      uuid: '2-3-4',
-      item: Promise.resolve({
-        uuid: '1-2-3',
-      }),
-      content: 'content2',
-    } as jest.Mocked<Revision>
-
-    revisionRepository.findByItemId = jest.fn().mockReturnValue([revision1, revision2])
-    revisionRepository.findMetadataByItemId = jest.fn().mockReturnValue([{} as jest.Mocked<RevisionMetadata>])
-    revisionRepository.findOneById = jest.fn().mockReturnValue(revision1)
-    revisionRepository.removeByUuid = jest.fn()
-  })
-
-  it('should not remove a revision for a non existing item', async () => {
-    itemRepository.findByUuid = jest.fn().mockReturnValue(null)
-
-    expect(
-      await createService().removeRevision({
-        itemUuid: '1-2-3',
-        userUuid: '1-2-3',
-        revisionUuid: '3-4-5',
-      }),
-    ).toBeFalsy()
-  })
-
-  it("should not remove a revision for another user's item", async () => {
-    itemRepository.findByUuid = jest.fn().mockReturnValue(null)
-
-    expect(
-      await createService().removeRevision({
-        itemUuid: '1-2-3',
-        userUuid: '3-4-5',
-        revisionUuid: '3-4-5',
-      }),
-    ).toBeFalsy()
-  })
-
-  it('should remove a revision if user has rights', async () => {
-    expect(
-      await createService().removeRevision({
-        itemUuid: '1-2-3',
-        userUuid: '1-2-3',
-        revisionUuid: '3-4-5',
-      }),
-    ).toBeTruthy()
-
-    expect(revisionRepository.removeByUuid).toHaveBeenCalledWith('1-2-3', '3-4-5')
-  })
-
-  it('should not get a revision for a non existing item', async () => {
-    itemRepository.findByUuid = jest.fn().mockReturnValue(null)
-
-    expect(
-      await createService().getRevision({
-        itemUuid: '1-2-3',
-        userRoles: [RoleName.NAMES.CoreUser],
-        userUuid: '1-2-3',
-        revisionUuid: '3-4-5',
-      }),
-    ).toBeNull()
-  })
-
-  it("should not get a revision for another user's item", async () => {
-    itemRepository.findByUuid = jest.fn().mockReturnValue(null)
-
-    expect(
-      await createService().getRevision({
-        itemUuid: '1-2-3',
-        userRoles: [RoleName.NAMES.CoreUser],
-        userUuid: '3-4-5',
-        revisionUuid: '3-4-5',
-      }),
-    ).toBeNull()
-  })
-
-  it('should not get a revision that does not exist', async () => {
-    revisionRepository.findOneById = jest.fn().mockReturnValue(null)
-
-    expect(
-      await createService().getRevision({
-        itemUuid: '1-2-3',
-        userRoles: [RoleName.NAMES.CoreUser],
-        userUuid: '1-2-3',
-        revisionUuid: '3-4-5',
-      }),
-    ).toBeNull()
-  })
-
-  it('should get a revision if user has enough permissions', async () => {
-    timer.dateWasNDaysAgo = jest.fn().mockReturnValue(2)
-
-    expect(
-      await createService().getRevision({
-        itemUuid: '1-2-3',
-        userRoles: [RoleName.NAMES.CoreUser],
-        userUuid: '1-2-3',
-        revisionUuid: '3-4-5',
-      }),
-    ).toEqual(revision1)
-  })
-
-  it('should not get a revision if user has not enough permissions - plus user', async () => {
-    timer.dateWasNDaysAgo = jest.fn().mockReturnValue(45)
-
-    expect(
-      await createService().getRevision({
-        itemUuid: '1-2-3',
-        userRoles: [RoleName.NAMES.CoreUser],
-        userUuid: '1-2-3',
-        revisionUuid: '3-4-5',
-      }),
-    ).toBeNull()
-  })
-
-  it('should not get a revision if user has not enough permissions - pro user', async () => {
-    timer.dateWasNDaysAgo = jest.fn().mockReturnValue(500)
-
-    expect(
-      await createService().getRevision({
-        itemUuid: '1-2-3',
-        userRoles: [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser],
-        userUuid: '1-2-3',
-        revisionUuid: '3-4-5',
-      }),
-    ).toBeNull()
-  })
-
-  it('should get revisions metadata for an item', async () => {
-    await createService().getRevisionsMetadata('1-2-3', '2-3-4')
-
-    expect(revisionRepository.findMetadataByItemId).toHaveBeenCalledWith('2-3-4')
-  })
-
-  it('should not get revisions metadata for an non existing item', async () => {
-    itemRepository.findByUuid = jest.fn().mockReturnValue(null)
-
-    expect(await createService().getRevisionsMetadata('1-2-3', '2-3-4')).toEqual([])
-
-    expect(revisionRepository.findMetadataByItemId).not.toHaveBeenCalled()
-  })
-
-  it("should not get revisions metadata for another user's item", async () => {
-    expect(await createService().getRevisionsMetadata('3-4-5', '4-5-6')).toEqual([])
-
-    expect(revisionRepository.findMetadataByItemId).not.toHaveBeenCalled()
-  })
-})

+ 0 - 89
packages/syncing-server/src/Domain/Revision/RevisionService.ts

@@ -1,89 +0,0 @@
-import { inject, injectable } from 'inversify'
-import { RoleName } from '@standardnotes/domain-core'
-import { TimerInterface } from '@standardnotes/time'
-
-import TYPES from '../../Bootstrap/Types'
-import { Revision } from './Revision'
-import { RevisionRepositoryInterface } from './RevisionRepositoryInterface'
-import { RevisionServiceInterface } from './RevisionServiceInterface'
-import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
-import { RevisionMetadata } from './RevisionMetadata'
-
-@injectable()
-export class RevisionService implements RevisionServiceInterface {
-  constructor(
-    @inject(TYPES.RevisionRepository) private revisionRepository: RevisionRepositoryInterface,
-    @inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
-    @inject(TYPES.Timer) private timer: TimerInterface,
-  ) {}
-
-  async removeRevision(dto: { userUuid: string; itemUuid: string; revisionUuid: string }): Promise<boolean> {
-    const userItem = await this.itemRepository.findByUuid(dto.itemUuid)
-    if (userItem === null || userItem.userUuid !== dto.userUuid) {
-      return false
-    }
-
-    await this.revisionRepository.removeByUuid(dto.itemUuid, dto.revisionUuid)
-
-    return true
-  }
-
-  async getRevisionsMetadata(userUuid: string, itemUuid: string): Promise<RevisionMetadata[]> {
-    const userItem = await this.itemRepository.findByUuid(itemUuid)
-    if (userItem === null || userItem.userUuid !== userUuid) {
-      return []
-    }
-
-    return this.revisionRepository.findMetadataByItemId(itemUuid)
-  }
-
-  async getRevision(dto: {
-    userUuid: string
-    userRoles: string[]
-    itemUuid: string
-    revisionUuid: string
-  }): Promise<Revision | null> {
-    const userItem = await this.itemRepository.findByUuid(dto.itemUuid)
-    if (userItem === null || userItem.userUuid !== dto.userUuid) {
-      return null
-    }
-
-    const revision = await this.revisionRepository.findOneById(dto.itemUuid, dto.revisionUuid)
-
-    if (revision !== null && !this.userHasEnoughPermissionsToSeeRevision(dto.userRoles, revision.createdAt)) {
-      return null
-    }
-
-    return revision
-  }
-
-  calculateRequiredRoleBasedOnRevisionDate(createdAt: Date): string {
-    const revisionCreatedNDaysAgo = this.timer.dateWasNDaysAgo(createdAt)
-
-    if (revisionCreatedNDaysAgo > 30 && revisionCreatedNDaysAgo < 365) {
-      return RoleName.NAMES.PlusUser
-    }
-
-    if (revisionCreatedNDaysAgo > 365) {
-      return RoleName.NAMES.ProUser
-    }
-
-    return RoleName.NAMES.CoreUser
-  }
-
-  private userHasEnoughPermissionsToSeeRevision(userRoles: string[], revisionCreatedAt: Date): boolean {
-    const roleRequired = this.calculateRequiredRoleBasedOnRevisionDate(revisionCreatedAt)
-
-    switch (roleRequired) {
-      case RoleName.NAMES.PlusUser:
-        return (
-          userRoles.filter((userRole) => [RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser].includes(userRole)).length >
-          0
-        )
-      case RoleName.NAMES.ProUser:
-        return userRoles.includes(RoleName.NAMES.ProUser)
-      default:
-        return true
-    }
-  }
-}

+ 0 - 14
packages/syncing-server/src/Domain/Revision/RevisionServiceInterface.ts

@@ -1,14 +0,0 @@
-import { Revision } from './Revision'
-import { RevisionMetadata } from './RevisionMetadata'
-
-export interface RevisionServiceInterface {
-  getRevisionsMetadata(userUuid: string, itemUuid: string): Promise<RevisionMetadata[]>
-  getRevision(dto: {
-    userUuid: string
-    userRoles: string[]
-    itemUuid: string
-    revisionUuid: string
-  }): Promise<Revision | null>
-  removeRevision(dto: { userUuid: string; itemUuid: string; revisionUuid: string }): Promise<boolean>
-  calculateRequiredRoleBasedOnRevisionDate(createdAt: Date): string
-}

+ 1 - 4
packages/syncing-server/src/Domain/UseCase/CheckIntegrity/CheckIntegrity.ts

@@ -1,17 +1,14 @@
-import { inject, injectable } from 'inversify'
 import { IntegrityPayload } from '@standardnotes/responses'
 import { ContentType } from '@standardnotes/common'
 
-import TYPES from '../../../Bootstrap/Types'
 import { ItemRepositoryInterface } from '../../Item/ItemRepositoryInterface'
 import { UseCaseInterface } from '../UseCaseInterface'
 import { CheckIntegrityDTO } from './CheckIntegrityDTO'
 import { CheckIntegrityResponse } from './CheckIntegrityResponse'
 import { ExtendedIntegrityPayload } from '../../Item/ExtendedIntegrityPayload'
 
-@injectable()
 export class CheckIntegrity implements UseCaseInterface {
-  constructor(@inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface) {}
+  constructor(private itemRepository: ItemRepositoryInterface) {}
 
   async execute(dto: CheckIntegrityDTO): Promise<CheckIntegrityResponse> {
     const serverItemIntegrityPayloads = await this.itemRepository.findItemsForComputingIntegrityPayloads(dto.userUuid)

+ 1 - 4
packages/syncing-server/src/Domain/UseCase/GetItem/GetItem.ts

@@ -1,13 +1,10 @@
-import { inject, injectable } from 'inversify'
-import TYPES from '../../../Bootstrap/Types'
 import { ItemRepositoryInterface } from '../../Item/ItemRepositoryInterface'
 import { UseCaseInterface } from '../UseCaseInterface'
 import { GetItemDTO } from './GetItemDTO'
 import { GetItemResponse } from './GetItemResponse'
 
-@injectable()
 export class GetItem implements UseCaseInterface {
-  constructor(@inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface) {}
+  constructor(private itemRepository: ItemRepositoryInterface) {}
 
   async execute(dto: GetItemDTO): Promise<GetItemResponse> {
     const item = await this.itemRepository.findByUuidAndUserUuid(dto.itemUuid, dto.userUuid)

+ 1 - 4
packages/syncing-server/src/Domain/UseCase/SyncItems.ts

@@ -1,5 +1,3 @@
-import { inject, injectable } from 'inversify'
-import TYPES from '../../Bootstrap/Types'
 import { Item } from '../Item/Item'
 import { ItemConflict } from '../Item/ItemConflict'
 import { ItemServiceInterface } from '../Item/ItemServiceInterface'
@@ -7,9 +5,8 @@ import { SyncItemsDTO } from './SyncItemsDTO'
 import { SyncItemsResponse } from './SyncItemsResponse'
 import { UseCaseInterface } from './UseCaseInterface'
 
-@injectable()
 export class SyncItems implements UseCaseInterface {
-  constructor(@inject(TYPES.ItemService) private itemService: ItemServiceInterface) {}
+  constructor(private itemService: ItemServiceInterface) {}
 
   async execute(dto: SyncItemsDTO): Promise<SyncItemsResponse> {
     const getItemsResult = await this.itemService.getItems({

+ 3 - 6
packages/syncing-server/src/Infra/FS/FSItemBackupService.ts

@@ -1,22 +1,19 @@
 import { KeyParamsData } from '@standardnotes/responses'
 import { promises } from 'fs'
 import * as uuid from 'uuid'
-import { inject, injectable } from 'inversify'
 import { Logger } from 'winston'
 import { dirname } from 'path'
 
-import TYPES from '../../Bootstrap/Types'
 import { Item } from '../../Domain/Item/Item'
 import { ItemBackupServiceInterface } from '../../Domain/Item/ItemBackupServiceInterface'
 import { ItemProjection } from '../../Projection/ItemProjection'
 import { ProjectorInterface } from '../../Projection/ProjectorInterface'
 
-@injectable()
 export class FSItemBackupService implements ItemBackupServiceInterface {
   constructor(
-    @inject(TYPES.FILE_UPLOAD_PATH) private fileUploadPath: string,
-    @inject(TYPES.ItemProjector) private itemProjector: ProjectorInterface<Item, ItemProjection>,
-    @inject(TYPES.Logger) private logger: Logger,
+    private fileUploadPath: string,
+    private itemProjector: ProjectorInterface<Item, ItemProjection>,
+    private logger: Logger,
   ) {}
 
   async backup(_items: Item[], _authParams: KeyParamsData): Promise<string> {

+ 2 - 7
packages/syncing-server/src/Infra/HTTP/AuthHttpService.ts

@@ -1,15 +1,10 @@
 import { KeyParamsData } from '@standardnotes/responses'
 import { AxiosInstance } from 'axios'
-import { inject, injectable } from 'inversify'
-import TYPES from '../../Bootstrap/Types'
+
 import { AuthHttpServiceInterface } from '../../Domain/Auth/AuthHttpServiceInterface'
 
-@injectable()
 export class AuthHttpService implements AuthHttpServiceInterface {
-  constructor(
-    @inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
-    @inject(TYPES.AUTH_SERVER_URL) private authServerUrl: string,
-  ) {}
+  constructor(private httpClient: AxiosInstance, private authServerUrl: string) {}
 
   async getUserSetting(userUuid: string, settingName: string): Promise<{ uuid: string; value: string | null }> {
     const response = await this.httpClient.request({

+ 1 - 7
packages/syncing-server/src/Infra/MySQL/MySQLItemRepository.ts

@@ -1,18 +1,12 @@
-import { inject, injectable } from 'inversify'
 import { Repository, SelectQueryBuilder } from 'typeorm'
 import { Item } from '../../Domain/Item/Item'
 import { ItemQuery } from '../../Domain/Item/ItemQuery'
 import { ItemRepositoryInterface } from '../../Domain/Item/ItemRepositoryInterface'
 import { ReadStream } from 'fs'
 import { ExtendedIntegrityPayload } from '../../Domain/Item/ExtendedIntegrityPayload'
-import TYPES from '../../Bootstrap/Types'
 
-@injectable()
 export class MySQLItemRepository implements ItemRepositoryInterface {
-  constructor(
-    @inject(TYPES.ORMItemRepository)
-    private ormRepository: Repository<Item>,
-  ) {}
+  constructor(private ormRepository: Repository<Item>) {}
 
   async save(item: Item): Promise<Item> {
     return this.ormRepository.save(item)

+ 0 - 83
packages/syncing-server/src/Infra/MySQL/MySQLRevisionRepository.ts

@@ -1,83 +0,0 @@
-/* istanbul ignore file */
-
-import { UniqueEntityId } from '@standardnotes/domain-core'
-import { inject, injectable } from 'inversify'
-import { Repository } from 'typeorm'
-import TYPES from '../../Bootstrap/Types'
-import { Revision } from '../../Domain/Revision/Revision'
-import { RevisionMetadata } from '../../Domain/Revision/RevisionMetadata'
-import { RevisionRepositoryInterface } from '../../Domain/Revision/RevisionRepositoryInterface'
-
-@injectable()
-export class MySQLRevisionRepository implements RevisionRepositoryInterface {
-  constructor(
-    @inject(TYPES.ORMRevisionRepository)
-    private ormRepository: Repository<Revision>,
-  ) {}
-
-  async save(revision: Revision): Promise<Revision> {
-    return this.ormRepository.save(revision)
-  }
-
-  async removeByUuid(itemUuid: string, revisionUuid: string): Promise<void> {
-    await this.ormRepository
-      .createQueryBuilder('revision')
-      .delete()
-      .from('revisions')
-      .where('uuid = :revisionUuid AND item_uuid = :itemUuid', { itemUuid, revisionUuid })
-      .execute()
-  }
-
-  async findByItemId(parameters: { itemUuid: string; afterDate?: Date }): Promise<Array<Revision>> {
-    const queryBuilder = this.ormRepository.createQueryBuilder('revision').where('revision.item_uuid = :item_uuid', {
-      item_uuid: parameters.itemUuid,
-    })
-
-    if (parameters.afterDate !== undefined) {
-      queryBuilder.andWhere('revision.creation_date >= :after_date', { after_date: parameters.afterDate })
-    }
-
-    return queryBuilder.orderBy('revision.created_at', 'DESC').getMany()
-  }
-
-  async findMetadataByItemId(itemUuid: string): Promise<Array<RevisionMetadata>> {
-    const queryBuilder = this.ormRepository
-      .createQueryBuilder()
-      .select('uuid', 'uuid')
-      .addSelect('content_type', 'contentType')
-      .addSelect('created_at', 'createdAt')
-      .addSelect('updated_at', 'updatedAt')
-      .where('item_uuid = :item_uuid', {
-        item_uuid: itemUuid,
-      })
-
-    const simplifiedRevisions = await queryBuilder.orderBy('created_at', 'DESC').getRawMany()
-
-    const metadata = []
-
-    for (const simplifiedRevision of simplifiedRevisions) {
-      const revisionMetadataOrError = RevisionMetadata.create(
-        {
-          contentType: simplifiedRevision.contentType,
-          updatedAt: simplifiedRevision.updatedAt,
-          createdAt: simplifiedRevision.createdAt,
-        },
-        new UniqueEntityId(simplifiedRevision.uuid),
-      )
-      if (revisionMetadataOrError.isFailed()) {
-        throw new Error(revisionMetadataOrError.getError())
-      }
-
-      metadata.push(revisionMetadataOrError.getValue())
-    }
-
-    return metadata
-  }
-
-  async findOneById(itemId: string, id: string): Promise<Revision | null> {
-    return this.ormRepository
-      .createQueryBuilder('revision')
-      .where('revision.uuid = :uuid AND revision.item_uuid = :item_uuid', { uuid: id, item_uuid: itemId })
-      .getOne()
-  }
-}

+ 4 - 7
packages/syncing-server/src/Infra/S3/S3ItemBackupService.ts

@@ -1,22 +1,19 @@
 import * as uuid from 'uuid'
 import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
 import { KeyParamsData } from '@standardnotes/responses'
-import { inject, injectable } from 'inversify'
 import { Logger } from 'winston'
 
-import TYPES from '../../Bootstrap/Types'
 import { Item } from '../../Domain/Item/Item'
 import { ItemBackupServiceInterface } from '../../Domain/Item/ItemBackupServiceInterface'
 import { ProjectorInterface } from '../../Projection/ProjectorInterface'
 import { ItemProjection } from '../../Projection/ItemProjection'
 
-@injectable()
 export class S3ItemBackupService implements ItemBackupServiceInterface {
   constructor(
-    @inject(TYPES.S3_BACKUP_BUCKET_NAME) private s3BackupBucketName: string,
-    @inject(TYPES.ItemProjector) private itemProjector: ProjectorInterface<Item, ItemProjection>,
-    @inject(TYPES.Logger) private logger: Logger,
-    @inject(TYPES.S3) private s3Client?: S3Client,
+    private s3BackupBucketName: string,
+    private itemProjector: ProjectorInterface<Item, ItemProjection>,
+    private logger: Logger,
+    private s3Client?: S3Client,
   ) {}
 
   async dump(item: Item): Promise<string> {

+ 1 - 4
packages/syncing-server/src/Projection/ItemConflictProjector.ts

@@ -1,5 +1,3 @@
-import { inject, injectable } from 'inversify'
-import TYPES from '../Bootstrap/Types'
 import { ProjectorInterface } from './ProjectorInterface'
 
 import { Item } from '../Domain/Item/Item'
@@ -7,9 +5,8 @@ import { ItemConflict } from '../Domain/Item/ItemConflict'
 import { ItemConflictProjection } from './ItemConflictProjection'
 import { ItemProjection } from './ItemProjection'
 
-@injectable()
 export class ItemConflictProjector implements ProjectorInterface<ItemConflict, ItemConflictProjection> {
-  constructor(@inject(TYPES.ItemProjector) private itemProjector: ProjectorInterface<Item, ItemProjection>) {}
+  constructor(private itemProjector: ProjectorInterface<Item, ItemProjection>) {}
 
   async projectSimple(_itemConflict: ItemConflict): Promise<ItemConflictProjection> {
     throw Error('not implemented')

+ 1 - 4
packages/syncing-server/src/Projection/ItemProjector.ts

@@ -1,15 +1,12 @@
 import { TimerInterface } from '@standardnotes/time'
-import { inject, injectable } from 'inversify'
-import TYPES from '../Bootstrap/Types'
 import { ProjectorInterface } from './ProjectorInterface'
 
 import { Item } from '../Domain/Item/Item'
 import { ItemProjection } from './ItemProjection'
 import { ItemProjectionWithUser } from './ItemProjectionWithUser'
 
-@injectable()
 export class ItemProjector implements ProjectorInterface<Item, ItemProjection> {
-  constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
+  constructor(private timer: TimerInterface) {}
 
   async projectSimple(_item: Item): Promise<ItemProjection> {
     throw Error('not implemented')

+ 0 - 15
packages/syncing-server/src/Projection/RevisionProjection.ts

@@ -1,15 +0,0 @@
-import { ContentType } from '@standardnotes/common'
-
-export type RevisionProjection = {
-  uuid: string
-  item_uuid: string
-  content: string | null
-  content_type: ContentType | null
-  items_key_id: string | null
-  enc_item_key: string | null
-  auth_hash: string | null
-  creation_date: string
-  required_role: string
-  created_at: string
-  updated_at: string
-}

+ 0 - 74
packages/syncing-server/src/Projection/RevisionProjector.spec.ts

@@ -1,74 +0,0 @@
-import { ContentType } from '@standardnotes/common'
-import { RoleName } from '@standardnotes/domain-core'
-import { TimerInterface } from '@standardnotes/time'
-import { Item } from '../Domain/Item/Item'
-
-import { Revision } from '../Domain/Revision/Revision'
-import { RevisionServiceInterface } from '../Domain/Revision/RevisionServiceInterface'
-import { RevisionProjector } from './RevisionProjector'
-
-describe('RevisionProjector', () => {
-  let revision: Revision
-  let timer: TimerInterface
-  let revisionService: RevisionServiceInterface
-
-  const createProjector = () => new RevisionProjector(timer, revisionService)
-
-  beforeEach(() => {
-    revision = new Revision()
-    revision.content = 'test'
-    revision.contentType = ContentType.Note
-    ;(revision.uuid = '123'),
-      (revision.itemsKeyId = '123'),
-      (revision.item = Promise.resolve({ uuid: '1-2-3' } as Item))
-
-    timer = {} as jest.Mocked<TimerInterface>
-    timer.convertDateToISOString = jest.fn().mockReturnValue('2020-11-26T13:34:00.000Z')
-    timer.formatDate = jest.fn().mockReturnValue('2020-11-26')
-
-    revisionService = {} as jest.Mocked<RevisionServiceInterface>
-    revisionService.calculateRequiredRoleBasedOnRevisionDate = jest.fn().mockReturnValue(RoleName.NAMES.CoreUser)
-
-    revision.creationDate = new Date(1)
-    revision.createdAt = new Date(1)
-    revision.updatedAt = new Date(1)
-  })
-
-  it('should create a simple projection of a revision', async () => {
-    const projection = await createProjector().projectSimple(revision)
-    expect(projection).toMatchObject({
-      content_type: 'Note',
-      created_at: '2020-11-26T13:34:00.000Z',
-      updated_at: '2020-11-26T13:34:00.000Z',
-      required_role: 'CORE_USER',
-      uuid: '123',
-    })
-  })
-
-  it('should create a full projection of a revision', async () => {
-    const projection = await createProjector().projectFull(revision)
-    expect(projection).toMatchObject({
-      auth_hash: undefined,
-      content: 'test',
-      content_type: 'Note',
-      created_at: '2020-11-26T13:34:00.000Z',
-      creation_date: '2020-11-26',
-      enc_item_key: undefined,
-      required_role: 'CORE_USER',
-      item_uuid: '1-2-3',
-      items_key_id: '123',
-      updated_at: '2020-11-26T13:34:00.000Z',
-      uuid: '123',
-    })
-  })
-
-  it('should throw error on not implemetned custom projection', async () => {
-    let error = null
-    try {
-      await createProjector().projectCustom('test', revision)
-    } catch (e) {
-      error = e
-    }
-    expect((error as Error).message).toEqual('not implemented')
-  })
-})

+ 0 - 47
packages/syncing-server/src/Projection/RevisionProjector.ts

@@ -1,47 +0,0 @@
-import { TimerInterface } from '@standardnotes/time'
-import { inject, injectable } from 'inversify'
-import TYPES from '../Bootstrap/Types'
-
-import { Revision } from '../Domain/Revision/Revision'
-import { RevisionServiceInterface } from '../Domain/Revision/RevisionServiceInterface'
-import { ProjectorInterface } from './ProjectorInterface'
-import { RevisionProjection } from './RevisionProjection'
-import { SimpleRevisionProjection } from './SimpleRevisionProjection'
-
-@injectable()
-export class RevisionProjector implements ProjectorInterface<Revision, RevisionProjection> {
-  constructor(
-    @inject(TYPES.Timer) private timer: TimerInterface,
-    @inject(TYPES.RevisionService) private revisionService: RevisionServiceInterface,
-  ) {}
-
-  async projectSimple(revision: Revision): Promise<SimpleRevisionProjection> {
-    return {
-      uuid: revision.uuid,
-      content_type: revision.contentType,
-      required_role: this.revisionService.calculateRequiredRoleBasedOnRevisionDate(revision.createdAt),
-      created_at: this.timer.convertDateToISOString(revision.createdAt),
-      updated_at: this.timer.convertDateToISOString(revision.updatedAt),
-    }
-  }
-
-  async projectFull(revision: Revision): Promise<RevisionProjection> {
-    return {
-      uuid: revision.uuid,
-      item_uuid: (await revision.item).uuid,
-      content: revision.content,
-      content_type: revision.contentType,
-      items_key_id: revision.itemsKeyId,
-      enc_item_key: revision.encItemKey,
-      auth_hash: revision.authHash,
-      creation_date: this.timer.formatDate(revision.creationDate, 'YYYY-MM-DD'),
-      required_role: this.revisionService.calculateRequiredRoleBasedOnRevisionDate(revision.createdAt),
-      created_at: this.timer.convertDateToISOString(revision.createdAt),
-      updated_at: this.timer.convertDateToISOString(revision.updatedAt),
-    }
-  }
-
-  async projectCustom(_projectionType: string, _revision: Revision, ..._args: any[]): Promise<RevisionProjection> {
-    throw new Error('not implemented')
-  }
-}

+ 3 - 5
packages/syncing-server/src/Projection/SavedItemProjector.ts

@@ -1,14 +1,12 @@
 import { TimerInterface } from '@standardnotes/time'
-import { inject, injectable } from 'inversify'
-import TYPES from '../Bootstrap/Types'
-import { ProjectorInterface } from './ProjectorInterface'
 
 import { Item } from '../Domain/Item/Item'
+
+import { ProjectorInterface } from './ProjectorInterface'
 import { SavedItemProjection } from './SavedItemProjection'
 
-@injectable()
 export class SavedItemProjector implements ProjectorInterface<Item, SavedItemProjection> {
-  constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
+  constructor(private timer: TimerInterface) {}
 
   async projectSimple(_item: Item): Promise<SavedItemProjection> {
     throw Error('not implemented')

+ 0 - 9
packages/syncing-server/src/Projection/SimpleRevisionProjection.ts

@@ -1,9 +0,0 @@
-import { ContentType } from '@standardnotes/common'
-
-export type SimpleRevisionProjection = {
-  uuid: string
-  content_type: ContentType | null
-  required_role: string
-  created_at: string
-  updated_at: string
-}

+ 0 - 2
yarn.lock

@@ -3772,7 +3772,6 @@ __metadata:
     "@types/dotenv": "npm:^8.2.0"
     "@types/express": "npm:^4.17.14"
     "@types/inversify-express-utils": "npm:^2.0.0"
-    "@types/ioredis": "npm:^5.0.0"
     "@types/jest": "npm:^29.1.1"
     "@types/jsonwebtoken": "npm:^9.0.1"
     "@types/newrelic": "npm:^9.4.0"
@@ -3789,7 +3788,6 @@ __metadata:
     helmet: "npm:^6.0.0"
     inversify: "npm:^6.0.1"
     inversify-express-utils: "npm:^6.4.3"
-    ioredis: "npm:^5.2.4"
     jest: "npm:^29.1.2"
     jsonwebtoken: "npm:^9.0.0"
     mysql2: "npm:^3.0.1"