package main import ( "context" "database/sql" "flag" "fmt" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" _ "github.com/lib/pq" amqp "github.com/rabbitmq/amqp091-go" "github.com/redis/go-redis/v9" "github.com/rwadurian/mpc-system/pkg/config" "github.com/rwadurian/mpc-system/pkg/jwt" "github.com/rwadurian/mpc-system/pkg/logger" httphandler "github.com/rwadurian/mpc-system/services/account/adapters/input/http" jwtadapter "github.com/rwadurian/mpc-system/services/account/adapters/output/jwt" "github.com/rwadurian/mpc-system/services/account/adapters/output/postgres" "github.com/rwadurian/mpc-system/services/account/adapters/output/rabbitmq" redisadapter "github.com/rwadurian/mpc-system/services/account/adapters/output/redis" "github.com/rwadurian/mpc-system/services/account/application/use_cases" "github.com/rwadurian/mpc-system/services/account/domain/services" "go.uber.org/zap" ) func main() { // Parse flags configPath := flag.String("config", "", "Path to config file") flag.Parse() // Load configuration cfg, err := config.Load(*configPath) if err != nil { fmt.Printf("Failed to load config: %v\n", err) os.Exit(1) } // Initialize logger if err := logger.Init(&logger.Config{ Level: cfg.Logger.Level, Encoding: cfg.Logger.Encoding, }); err != nil { fmt.Printf("Failed to initialize logger: %v\n", err) os.Exit(1) } defer logger.Sync() logger.Info("Starting Account Service", zap.String("environment", cfg.Server.Environment), zap.Int("http_port", cfg.Server.HTTPPort)) // Initialize database connection db, err := initDatabase(cfg.Database) if err != nil { logger.Fatal("Failed to connect to database", zap.Error(err)) } defer db.Close() // Initialize Redis connection redisClient := initRedis(cfg.Redis) defer redisClient.Close() // Initialize RabbitMQ connection rabbitConn, err := initRabbitMQ(cfg.RabbitMQ) if err != nil { logger.Fatal("Failed to connect to RabbitMQ", zap.Error(err)) } defer rabbitConn.Close() // Initialize repositories accountRepo := postgres.NewAccountPostgresRepo(db) shareRepo := postgres.NewAccountSharePostgresRepo(db) recoveryRepo := postgres.NewRecoverySessionPostgresRepo(db) // Initialize adapters eventPublisher, err := rabbitmq.NewEventPublisherAdapter(rabbitConn) if err != nil { logger.Fatal("Failed to create event publisher", zap.Error(err)) } defer eventPublisher.Close() cacheAdapter := redisadapter.NewCacheAdapter(redisClient) // Initialize JWT service jwtService := jwt.NewJWTService( cfg.JWT.SecretKey, cfg.JWT.Issuer, cfg.JWT.TokenExpiry, cfg.JWT.RefreshExpiry, ) tokenService := jwtadapter.NewTokenServiceAdapter(jwtService) // Initialize domain service domainService := services.NewAccountDomainService(accountRepo, shareRepo, recoveryRepo) // Initialize use cases createAccountUC := use_cases.NewCreateAccountUseCase(accountRepo, shareRepo, domainService, eventPublisher) getAccountUC := use_cases.NewGetAccountUseCase(accountRepo, shareRepo) updateAccountUC := use_cases.NewUpdateAccountUseCase(accountRepo, eventPublisher) listAccountsUC := use_cases.NewListAccountsUseCase(accountRepo) getAccountSharesUC := use_cases.NewGetAccountSharesUseCase(accountRepo, shareRepo) deactivateShareUC := use_cases.NewDeactivateShareUseCase(accountRepo, shareRepo, eventPublisher) loginUC := use_cases.NewLoginUseCase(accountRepo, shareRepo, tokenService, eventPublisher) refreshTokenUC := use_cases.NewRefreshTokenUseCase(accountRepo, tokenService) generateChallengeUC := use_cases.NewGenerateChallengeUseCase(cacheAdapter) initiateRecoveryUC := use_cases.NewInitiateRecoveryUseCase(accountRepo, recoveryRepo, domainService, eventPublisher) completeRecoveryUC := use_cases.NewCompleteRecoveryUseCase(accountRepo, shareRepo, recoveryRepo, domainService, eventPublisher) getRecoveryStatusUC := use_cases.NewGetRecoveryStatusUseCase(recoveryRepo) cancelRecoveryUC := use_cases.NewCancelRecoveryUseCase(accountRepo, recoveryRepo) // Create shutdown context ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Start HTTP server errChan := make(chan error, 1) go func() { if err := startHTTPServer( cfg, createAccountUC, getAccountUC, updateAccountUC, listAccountsUC, getAccountSharesUC, deactivateShareUC, loginUC, refreshTokenUC, generateChallengeUC, initiateRecoveryUC, completeRecoveryUC, getRecoveryStatusUC, cancelRecoveryUC, ); err != nil { errChan <- fmt.Errorf("HTTP server error: %w", err) } }() // Wait for shutdown signal sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) select { case sig := <-sigChan: logger.Info("Received shutdown signal", zap.String("signal", sig.String())) case err := <-errChan: logger.Error("Server error", zap.Error(err)) } // Graceful shutdown logger.Info("Shutting down...") cancel() // Give services time to shutdown gracefully time.Sleep(5 * time.Second) logger.Info("Shutdown complete") _ = ctx } func initDatabase(cfg config.DatabaseConfig) (*sql.DB, error) { db, err := sql.Open("postgres", cfg.DSN()) if err != nil { return nil, err } db.SetMaxOpenConns(cfg.MaxOpenConns) db.SetMaxIdleConns(cfg.MaxIdleConns) db.SetConnMaxLifetime(cfg.ConnMaxLife) // Test connection if err := db.Ping(); err != nil { return nil, err } logger.Info("Connected to PostgreSQL") return db, nil } func initRedis(cfg config.RedisConfig) *redis.Client { client := redis.NewClient(&redis.Options{ Addr: cfg.Addr(), Password: cfg.Password, DB: cfg.DB, }) // Test connection ctx := context.Background() if err := client.Ping(ctx).Err(); err != nil { logger.Warn("Redis connection failed, continuing without cache", zap.Error(err)) } else { logger.Info("Connected to Redis") } return client } func initRabbitMQ(cfg config.RabbitMQConfig) (*amqp.Connection, error) { conn, err := amqp.Dial(cfg.URL()) if err != nil { return nil, err } logger.Info("Connected to RabbitMQ") return conn, nil } func startHTTPServer( cfg *config.Config, createAccountUC *use_cases.CreateAccountUseCase, getAccountUC *use_cases.GetAccountUseCase, updateAccountUC *use_cases.UpdateAccountUseCase, listAccountsUC *use_cases.ListAccountsUseCase, getAccountSharesUC *use_cases.GetAccountSharesUseCase, deactivateShareUC *use_cases.DeactivateShareUseCase, loginUC *use_cases.LoginUseCase, refreshTokenUC *use_cases.RefreshTokenUseCase, generateChallengeUC *use_cases.GenerateChallengeUseCase, initiateRecoveryUC *use_cases.InitiateRecoveryUseCase, completeRecoveryUC *use_cases.CompleteRecoveryUseCase, getRecoveryStatusUC *use_cases.GetRecoveryStatusUseCase, cancelRecoveryUC *use_cases.CancelRecoveryUseCase, ) error { // Set Gin mode if cfg.Server.Environment == "production" { gin.SetMode(gin.ReleaseMode) } router := gin.New() router.Use(gin.Recovery()) router.Use(gin.Logger()) // Create HTTP handler httpHandler := httphandler.NewAccountHTTPHandler( createAccountUC, getAccountUC, updateAccountUC, listAccountsUC, getAccountSharesUC, deactivateShareUC, loginUC, refreshTokenUC, generateChallengeUC, initiateRecoveryUC, completeRecoveryUC, getRecoveryStatusUC, cancelRecoveryUC, ) // Health check router.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "healthy", "service": "account", }) }) // Register API routes api := router.Group("/api/v1") httpHandler.RegisterRoutes(api) logger.Info("Starting HTTP server", zap.Int("port", cfg.Server.HTTPPort)) return router.Run(fmt.Sprintf(":%d", cfg.Server.HTTPPort)) }