package app import ( "context" "errors" "gitea.pena/PenaSide/codeword/internal/controller/admin/admin_promocode" "gitea.pena/PenaSide/codeword/internal/controller/admin/admin_recovery" "gitea.pena/PenaSide/codeword/internal/controller/client/client_promocode" "gitea.pena/PenaSide/codeword/internal/controller/client/client_recovery" "gitea.pena/PenaSide/codeword/internal/controller/rpc_controllers" "gitea.pena/PenaSide/codeword/internal/initialize" "gitea.pena/PenaSide/codeword/internal/models" "gitea.pena/PenaSide/codeword/internal/repository" "gitea.pena/PenaSide/codeword/internal/server/grpc" httpserver "gitea.pena/PenaSide/codeword/internal/server/http" "gitea.pena/PenaSide/codeword/internal/services" "gitea.pena/PenaSide/codeword/internal/utils/middleware" "gitea.pena/PenaSide/codeword/internal/worker/purge_worker" "gitea.pena/PenaSide/codeword/internal/worker/recovery_worker" "gitea.pena/PenaSide/codeword/pkg/closer" "gitea.pena/PenaSide/hlog" "gitea.pena/PenaSide/trashlog/app" "gitea.pena/PenaSide/trashlog/wrappers/zaptrashlog" "github.com/twmb/franz-go/pkg/kgo" "go.uber.org/zap" "go.uber.org/zap/zapcore" "time" ) type Build struct { Commit string Version string BuildTime int64 } func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger, build Build) error { defer func() { if r := recover(); r != nil { logger.Error("Recovered from a panic", zap.Any("error", r)) } }() logger.Info("Starting application", zap.String("AppName", cfg.AppName)) ctx, cancel := context.WithCancel(ctx) defer cancel() clickHouseLogger, err := zaptrashlog.NewCore(ctx, zap.InfoLevel, cfg.TrashLogHost, build.Version, build.Commit, build.BuildTime) if err != nil { panic(err) } loggerForHlog := logger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core { return zapcore.NewTee(core, clickHouseLogger) })) loggerHlog := hlog.New(loggerForHlog).Module(initialize.ModuleLogger) loggerHlog.With(models.AllFields{}) loggerHlog.Emit(app.InfoSvcStarted{}) shutdownGroup := closer.NewCloserGroup() mdb, err := initialize.MongoDB(ctx, cfg) if err != nil { logger.Error("Failed to initialize MongoDB", zap.Error(err)) return err } if err = initialize.InitDatabaseIndexes(ctx, mdb, logger); err != nil { logger.Error("Failed to initialize db indexes", zap.Error(err)) return err } kafkaTariffClient, err := kgo.NewClient( kgo.SeedBrokers(cfg.KafkaBrokers...), kgo.ConsumeResetOffset(kgo.NewOffset().AtStart()), kgo.DefaultProduceTopic(cfg.KafkaTopicTariff), ) if err != nil { return err } err = kafkaTariffClient.Ping(ctx) if err != nil { return err } discountRpcClient, err := initialize.DiscountGRPCClient(cfg.DiscountMicroserviceGRPC) if err != nil { logger.Error("failed to connect to discount service", zap.Error(err)) return err } brokers := initialize.NewBrokers(initialize.BrokersDeps{ Logger: logger, TariffClient: kafkaTariffClient, Topic: cfg.KafkaTopicTariff, }) rdb, err := initialize.Redis(ctx, cfg) if err != nil { logger.Error("failed to connect to redis db", zap.Error(err)) return err } encrypt := initialize.Encrypt(cfg) promoCodeRepo := repository.NewPromoCodeRepository(mdb.Collection("promoCodes")) statsRepo := repository.NewStatsRepository(repository.Deps{Rdb: nil, Mdb: mdb.Collection("promoStats")}) codewordRepo := repository.NewCodewordRepository(repository.Deps{Rdb: rdb, Mdb: mdb.Collection("codeword")}) userRepo := repository.NewUserRepository(repository.Deps{Rdb: nil, Mdb: mdb.Collection("users")}) recoveryEmailSender := initialize.RecoveryEmailSender(cfg, logger) authClient := initialize.AuthClient(cfg, logger) recoveryService := services.NewRecoveryService(services.Deps{ Logger: logger, CodewordRepository: codewordRepo, UserRepository: userRepo, Encrypt: encrypt, AuthClient: authClient, }) promoService := services.NewPromoCodeService(services.PromoDeps{ Logger: logger, PromoCodeRepo: promoCodeRepo, StatsRepo: statsRepo, Kafka: brokers.TariffProducer, DiscountClient: discountRpcClient, }) jwtUtil := middleware.NewJWT(&cfg) clientRecoveryController := client_recovery.NewRecoveryController(client_recovery.Deps{ Logger: logger, Service: recoveryService, DefaultURL: cfg.DefaultRedirectionURL, RecoveryURL: cfg.MailRecoveryURL, }) clientPromoCodeController := client_promocode.NewPromoCodeController(client_promocode.Deps{Logger: logger, PromoCodeService: promoService}) adminRecoveryController := admin_recovery.NewRecoveryController(admin_recovery.Deps{ Logger: logger, Service: recoveryService, }) adminPromoCodeController := admin_promocode.NewPromoCodeController(admin_promocode.Deps{Logger: logger, PromoCodeService: promoService}) controllerRpc := rpc_controllers.InitRpcControllers(promoService) grpcServer, err := grpc.NewGRPC(logger) if err != nil { logger.Error("error init rpc server", zap.Error(err)) return err } grpcServer.Register(controllerRpc) recoveryWC := recovery_worker.NewRecoveryWC(recovery_worker.Deps{ Logger: logger, Redis: rdb, EmailSender: recoveryEmailSender, Mongo: mdb.Collection("codeword"), }) purgeWC := purge_worker.NewPurgeWC(purge_worker.Deps{ Logger: logger, Mongo: mdb.Collection("codeword"), }) go recoveryWC.Start(ctx) go purgeWC.Start(ctx) clientServer := httpserver.NewServer(httpserver.ServerConfig{ Logger: logger, Controllers: []httpserver.Controller{clientRecoveryController, clientPromoCodeController}, Hlogger: loggerHlog, JWT: jwtUtil, }) adminServer := httpserver.NewServer(httpserver.ServerConfig{ Logger: logger, Controllers: []httpserver.Controller{adminRecoveryController, adminPromoCodeController}, Hlogger: loggerHlog, JWT: jwtUtil, }) go func() { if err := clientServer.Start(cfg.ClientHttpURL); err != nil { logger.Error("Client server startup error", zap.Error(err)) cancel() } }() go func() { if err := adminServer.Start(cfg.AdminHttpURL); err != nil { logger.Error("Admin server startup error", zap.Error(err)) cancel() } }() go grpcServer.Run(cfg.GrpcURL) clientServer.ListRoutes() adminServer.ListRoutes() shutdownGroup.Add(closer.CloserFunc(clientServer.Shutdown)) shutdownGroup.Add(closer.CloserFunc(adminServer.Shutdown)) shutdownGroup.Add(closer.CloserFunc(grpcServer.Stop)) shutdownGroup.Add(closer.CloserFunc(mdb.Client().Disconnect)) shutdownGroup.Add(closer.CloserFunc(recoveryWC.Stop)) shutdownGroup.Add(closer.CloserFunc(purgeWC.Stop)) <-ctx.Done() timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 10*time.Second) defer timeoutCancel() if err := shutdownGroup.Call(timeoutCtx); err != nil { if errors.Is(err, context.DeadlineExceeded) { logger.Error("Shutdown timed out", zap.Error(err)) } else { logger.Error("Failed to shutdown services gracefully", zap.Error(err)) } return err } logger.Info("Application has stopped") return nil }