import * as winston from 'winston' import Redis from 'ioredis' import { Container } from 'inversify' import { DomainEventHandlerInterface, DomainEventMessageHandlerInterface, DomainEventPublisherInterface, DomainEventSubscriberInterface, } from '@standardnotes/domain-events' import { MapperInterface } from '@standardnotes/domain-core' // eslint-disable-next-line @typescript-eslint/no-var-requires const Mixpanel = require('mixpanel') import { Env } from './Env' import TYPES from './Types' import { AppDataSource } from './DataSource' import { DomainEventFactory } from '../Domain/Event/DomainEventFactory' import { SNSDomainEventPublisher, SQSDomainEventSubscriber, SQSEventMessageHandler, } from '@standardnotes/domain-events-infra' import { Timer, TimerInterface } from '@standardnotes/time' import { PeriodKeyGeneratorInterface } from '../Domain/Time/PeriodKeyGeneratorInterface' import { PeriodKeyGenerator } from '../Domain/Time/PeriodKeyGenerator' import { AnalyticsStoreInterface } from '../Domain/Analytics/AnalyticsStoreInterface' import { RedisAnalyticsStore } from '../Infra/Redis/RedisAnalyticsStore' import { StatisticsStoreInterface } from '../Domain/Statistics/StatisticsStoreInterface' import { RedisStatisticsStore } from '../Infra/Redis/RedisStatisticsStore' import { AnalyticsEntityRepositoryInterface } from '../Domain/Entity/AnalyticsEntityRepositoryInterface' import { MySQLAnalyticsEntityRepository } from '../Infra/MySQL/MySQLAnalyticsEntityRepository' import { Repository } from 'typeorm' import { AnalyticsEntity } from '../Domain/Entity/AnalyticsEntity' import { GetUserAnalyticsId } from '../Domain/UseCase/GetUserAnalyticsId/GetUserAnalyticsId' import { UserRegisteredEventHandler } from '../Domain/Handler/UserRegisteredEventHandler' import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountDeletionRequestedEventHandler' import { PaymentFailedEventHandler } from '../Domain/Handler/PaymentFailedEventHandler' import { PaymentSuccessEventHandler } from '../Domain/Handler/PaymentSuccessEventHandler' import { SubscriptionCancelledEventHandler } from '../Domain/Handler/SubscriptionCancelledEventHandler' import { SubscriptionRenewedEventHandler } from '../Domain/Handler/SubscriptionRenewedEventHandler' import { SubscriptionRefundedEventHandler } from '../Domain/Handler/SubscriptionRefundedEventHandler' import { SubscriptionPurchasedEventHandler } from '../Domain/Handler/SubscriptionPurchasedEventHandler' import { SubscriptionExpiredEventHandler } from '../Domain/Handler/SubscriptionExpiredEventHandler' import { SubscriptionReactivatedEventHandler } from '../Domain/Handler/SubscriptionReactivatedEventHandler' import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEventHandler' import { RevenueModificationRepositoryInterface } from '../Domain/Revenue/RevenueModificationRepositoryInterface' import { MySQLRevenueModificationRepository } from '../Infra/MySQL/MySQLRevenueModificationRepository' import { TypeORMRevenueModification } from '../Infra/TypeORM/TypeORMRevenueModification' import { RevenueModification } from '../Domain/Revenue/RevenueModification' import { RevenueModificationMap } from '../Domain/Map/RevenueModificationMap' import { SaveRevenueModification } from '../Domain/UseCase/SaveRevenueModification/SaveRevenueModification' import { CalculateMonthlyRecurringRevenue } from '../Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue' import { PersistStatistic } from '../Domain/UseCase/PersistStatistic/PersistStatistic' import { StatisticMeasureRepositoryInterface } from '../Domain/Statistics/StatisticMeasureRepositoryInterface' import { StatisticPersistenceRequestedEventHandler } from '../Domain/Handler/StatisticPersistenceRequestedEventHandler' import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns' import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs' import { SessionCreatedEventHandler } from '../Domain/Handler/SessionCreatedEventHandler' import { SessionRefreshedEventHandler } from '../Domain/Handler/SessionRefreshedEventHandler' export class ContainerConfigLoader { async load(): Promise { 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 winstonFormatters = [winston.format.splat(), winston.format.json()] const logger = winston.createLogger({ level: env.get('LOG_LEVEL', true) || 'info', format: winston.format.combine(...winstonFormatters), transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL', true) || 'info' })], }) container.bind(TYPES.Logger).toConstantValue(logger) 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(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(TYPES.SQS).toConstantValue(new SQSClient(sqsConfig)) } // 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.ADMIN_EMAILS).toConstantValue(env.get('ADMIN_EMAILS').split(',')) container.bind(TYPES.MIXPANEL_TOKEN).toConstantValue(env.get('MIXPANEL_TOKEN', true)) // Services container.bind(TYPES.DomainEventFactory).to(DomainEventFactory) container.bind(TYPES.PeriodKeyGenerator).toConstantValue(new PeriodKeyGenerator()) container .bind(TYPES.AnalyticsStore) .toConstantValue(new RedisAnalyticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis))) container .bind(TYPES.StatisticsStore) .toConstantValue(new RedisStatisticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis))) container.bind(TYPES.Timer).toConstantValue(new Timer()) container .bind(TYPES.DomainEventPublisher) .toConstantValue(new SNSDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN))) if (env.get('MIXPANEL_TOKEN', true)) { container.bind(TYPES.MixpanelClient).toConstantValue(Mixpanel.init(env.get('MIXPANEL_TOKEN', true))) } // Repositories container .bind(TYPES.AnalyticsEntityRepository) .to(MySQLAnalyticsEntityRepository) container .bind(TYPES.RevenueModificationRepository) .to(MySQLRevenueModificationRepository) container .bind(TYPES.StatisticMeasureRepository) .toConstantValue(new RedisStatisticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis))) // ORM container .bind>(TYPES.ORMAnalyticsEntityRepository) .toConstantValue(AppDataSource.getRepository(AnalyticsEntity)) container .bind>(TYPES.ORMRevenueModificationRepository) .toConstantValue(AppDataSource.getRepository(TypeORMRevenueModification)) // Use Case container.bind(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId) container.bind(TYPES.SaveRevenueModification).to(SaveRevenueModification) container .bind(TYPES.CalculateMonthlyRecurringRevenue) .to(CalculateMonthlyRecurringRevenue) container .bind(TYPES.PersistStatistic) .toConstantValue(new PersistStatistic(container.get(TYPES.StatisticMeasureRepository))) // Hanlders container.bind(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler) container .bind(TYPES.AccountDeletionRequestedEventHandler) .to(AccountDeletionRequestedEventHandler) container.bind(TYPES.PaymentFailedEventHandler).to(PaymentFailedEventHandler) container.bind(TYPES.PaymentSuccessEventHandler).to(PaymentSuccessEventHandler) container.bind(TYPES.SessionCreatedEventHandler).to(SessionCreatedEventHandler) container.bind(TYPES.SessionRefreshedEventHandler).to(SessionRefreshedEventHandler) container .bind(TYPES.SubscriptionCancelledEventHandler) .to(SubscriptionCancelledEventHandler) container .bind(TYPES.SubscriptionRenewedEventHandler) .to(SubscriptionRenewedEventHandler) container .bind(TYPES.SubscriptionRefundedEventHandler) .to(SubscriptionRefundedEventHandler) container .bind(TYPES.SubscriptionPurchasedEventHandler) .to(SubscriptionPurchasedEventHandler) container .bind(TYPES.SubscriptionExpiredEventHandler) .to(SubscriptionExpiredEventHandler) container .bind(TYPES.SubscriptionReactivatedEventHandler) .to(SubscriptionReactivatedEventHandler) container.bind(TYPES.RefundProcessedEventHandler).to(RefundProcessedEventHandler) container .bind(TYPES.StatisticPersistenceRequestedEventHandler) .toConstantValue( new StatisticPersistenceRequestedEventHandler( container.get(TYPES.PersistStatistic), container.get(TYPES.Timer), container.get(TYPES.Logger), env.get('MIXPANEL_TOKEN', true) ? container.get(TYPES.MixpanelClient) : null, ), ) // Maps container .bind>(TYPES.RevenueModificationMap) .to(RevenueModificationMap) const eventHandlers: Map = new Map([ ['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)], ['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)], ['PAYMENT_FAILED', container.get(TYPES.PaymentFailedEventHandler)], ['PAYMENT_SUCCESS', container.get(TYPES.PaymentSuccessEventHandler)], ['SUBSCRIPTION_CANCELLED', container.get(TYPES.SubscriptionCancelledEventHandler)], ['SUBSCRIPTION_RENEWED', container.get(TYPES.SubscriptionRenewedEventHandler)], ['SUBSCRIPTION_REFUNDED', container.get(TYPES.SubscriptionRefundedEventHandler)], ['SUBSCRIPTION_PURCHASED', container.get(TYPES.SubscriptionPurchasedEventHandler)], ['SUBSCRIPTION_EXPIRED', container.get(TYPES.SubscriptionExpiredEventHandler)], ['SUBSCRIPTION_REACTIVATED', container.get(TYPES.SubscriptionReactivatedEventHandler)], ['REFUND_PROCESSED', container.get(TYPES.RefundProcessedEventHandler)], ['STATISTIC_PERSISTENCE_REQUESTED', container.get(TYPES.StatisticPersistenceRequestedEventHandler)], ['SESSION_CREATED', container.get(TYPES.SessionCreatedEventHandler)], ['SESSION_REFRESHED', container.get(TYPES.SessionRefreshedEventHandler)], ]) container .bind(TYPES.DomainEventMessageHandler) .toConstantValue(new SQSEventMessageHandler(eventHandlers, container.get(TYPES.Logger))) container .bind(TYPES.DomainEventSubscriber) .toConstantValue( new SQSDomainEventSubscriber( container.get(TYPES.SQS), container.get(TYPES.SQS_QUEUE_URL), container.get(TYPES.DomainEventMessageHandler), container.get(TYPES.Logger), ), ) return container } }