Container.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import * as winston from 'winston'
  2. // eslint-disable-next-line @typescript-eslint/no-var-requires
  3. const axios = require('axios')
  4. import { AxiosInstance } from 'axios'
  5. import Redis from 'ioredis'
  6. import { Container } from 'inversify'
  7. import { Timer, TimerInterface } from '@standardnotes/time'
  8. import { Env } from './Env'
  9. import { TYPES } from './Types'
  10. import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface'
  11. import { HttpServiceProxy } from '../Service/Http/HttpServiceProxy'
  12. import { SubscriptionTokenAuthMiddleware } from '../Controller/SubscriptionTokenAuthMiddleware'
  13. import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
  14. import { RedisCrossServiceTokenCache } from '../Infra/Redis/RedisCrossServiceTokenCache'
  15. import { WebSocketAuthMiddleware } from '../Controller/WebSocketAuthMiddleware'
  16. import { InMemoryCrossServiceTokenCache } from '../Infra/InMemory/InMemoryCrossServiceTokenCache'
  17. import { DirectCallServiceProxy } from '../Service/Proxy/DirectCallServiceProxy'
  18. import { ServiceContainerInterface, ServiceIdentifier } from '@standardnotes/domain-core'
  19. import { EndpointResolverInterface } from '../Service/Resolver/EndpointResolverInterface'
  20. import { EndpointResolver } from '../Service/Resolver/EndpointResolver'
  21. import { RequiredCrossServiceTokenMiddleware } from '../Controller/RequiredCrossServiceTokenMiddleware'
  22. import { OptionalCrossServiceTokenMiddleware } from '../Controller/OptionalCrossServiceTokenMiddleware'
  23. import { Transform } from 'stream'
  24. import { OpenTelemetrySDK, OpenTelemetrySDKInterface } from '@standardnotes/domain-events-infra'
  25. export class ContainerConfigLoader {
  26. async load(configuration?: {
  27. serviceContainer?: ServiceContainerInterface
  28. logger?: Transform
  29. environmentOverrides?: { [name: string]: string }
  30. }): Promise<Container> {
  31. const env: Env = new Env(configuration?.environmentOverrides)
  32. env.load()
  33. const container = new Container()
  34. const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
  35. const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted'
  36. const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting
  37. const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
  38. container
  39. .bind<boolean>(TYPES.ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING)
  40. .toConstantValue(isConfiguredForHomeServerOrSelfHosting)
  41. if (!isConfiguredForHomeServerOrSelfHosting) {
  42. container
  43. .bind<OpenTelemetrySDKInterface>(TYPES.ApiGateway_OpenTelemetrySDK)
  44. .toConstantValue(new OpenTelemetrySDK(ServiceIdentifier.NAMES.ApiGateway))
  45. }
  46. const winstonFormatters = [winston.format.splat(), winston.format.json()]
  47. if (env.get('NEW_RELIC_ENABLED', true) === 'true') {
  48. await import('newrelic')
  49. // eslint-disable-next-line @typescript-eslint/no-var-requires
  50. const newrelicFormatter = require('@newrelic/winston-enricher')
  51. const newrelicWinstonFormatter = newrelicFormatter(winston)
  52. winstonFormatters.push(newrelicWinstonFormatter())
  53. }
  54. let logger: winston.Logger
  55. if (configuration?.logger) {
  56. logger = configuration.logger as winston.Logger
  57. } else {
  58. logger = winston.createLogger({
  59. level: env.get('LOG_LEVEL', true) || 'info',
  60. format: winston.format.combine(...winstonFormatters),
  61. transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL', true) || 'info' })],
  62. defaultMeta: { service: 'api-gateway' },
  63. })
  64. }
  65. container.bind<winston.Logger>(TYPES.ApiGateway_Logger).toConstantValue(logger)
  66. if (!isConfiguredForInMemoryCache) {
  67. const redisUrl = env.get('REDIS_URL')
  68. const isRedisInClusterMode = redisUrl.indexOf(',') > 0
  69. let redis
  70. if (isRedisInClusterMode) {
  71. redis = new Redis.Cluster(redisUrl.split(','))
  72. } else {
  73. redis = new Redis(redisUrl)
  74. }
  75. container.bind(TYPES.ApiGateway_Redis).toConstantValue(redis)
  76. }
  77. container.bind<AxiosInstance>(TYPES.ApiGateway_HTTPClient).toConstantValue(axios.create())
  78. // env vars
  79. container.bind(TYPES.ApiGateway_SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL', true))
  80. container.bind(TYPES.ApiGateway_AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL', true))
  81. container.bind(TYPES.ApiGateway_REVISIONS_SERVER_URL).toConstantValue(env.get('REVISIONS_SERVER_URL', true))
  82. container.bind(TYPES.ApiGateway_EMAIL_SERVER_URL).toConstantValue(env.get('EMAIL_SERVER_URL', true))
  83. container.bind(TYPES.ApiGateway_PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
  84. container.bind(TYPES.ApiGateway_FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
  85. container.bind(TYPES.ApiGateway_WEB_SOCKET_SERVER_URL).toConstantValue(env.get('WEB_SOCKET_SERVER_URL', true))
  86. container.bind(TYPES.ApiGateway_AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
  87. container
  88. .bind(TYPES.ApiGateway_HTTP_CALL_TIMEOUT)
  89. .toConstantValue(env.get('HTTP_CALL_TIMEOUT', true) ? +env.get('HTTP_CALL_TIMEOUT', true) : 60_000)
  90. container.bind(TYPES.ApiGateway_VERSION).toConstantValue(env.get('VERSION', true) ?? 'development')
  91. container
  92. .bind(TYPES.ApiGateway_CROSS_SERVICE_TOKEN_CACHE_TTL)
  93. .toConstantValue(+env.get('CROSS_SERVICE_TOKEN_CACHE_TTL', true))
  94. container.bind(TYPES.ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER).toConstantValue(isConfiguredForHomeServer)
  95. // Middleware
  96. container
  97. .bind<RequiredCrossServiceTokenMiddleware>(TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
  98. .to(RequiredCrossServiceTokenMiddleware)
  99. container
  100. .bind<OptionalCrossServiceTokenMiddleware>(TYPES.ApiGateway_OptionalCrossServiceTokenMiddleware)
  101. .to(OptionalCrossServiceTokenMiddleware)
  102. container.bind<WebSocketAuthMiddleware>(TYPES.ApiGateway_WebSocketAuthMiddleware).to(WebSocketAuthMiddleware)
  103. container
  104. .bind<SubscriptionTokenAuthMiddleware>(TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
  105. .to(SubscriptionTokenAuthMiddleware)
  106. // Services
  107. if (isConfiguredForHomeServer) {
  108. if (!configuration?.serviceContainer) {
  109. throw new Error('Service container is required when configured for home server')
  110. }
  111. container
  112. .bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy)
  113. .toConstantValue(
  114. new DirectCallServiceProxy(configuration.serviceContainer, container.get(TYPES.ApiGateway_FILES_SERVER_URL)),
  115. )
  116. } else {
  117. container.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy).to(HttpServiceProxy)
  118. }
  119. container.bind<TimerInterface>(TYPES.ApiGateway_Timer).toConstantValue(new Timer())
  120. if (isConfiguredForHomeServer) {
  121. container
  122. .bind<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache)
  123. .toConstantValue(new InMemoryCrossServiceTokenCache(container.get(TYPES.ApiGateway_Timer)))
  124. } else {
  125. container
  126. .bind<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache)
  127. .to(RedisCrossServiceTokenCache)
  128. }
  129. container
  130. .bind<EndpointResolverInterface>(TYPES.ApiGateway_EndpointResolver)
  131. .toConstantValue(new EndpointResolver(isConfiguredForHomeServer))
  132. logger.debug('Configuration complete')
  133. return container
  134. }
  135. }