rwadurian/backend/mpc-system/services/account/cmd/server/main.go

270 lines
7.5 KiB
Go

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))
}