package app import ( "context" "fmt" "net/http" "time" "mengstack/internal/app/cache" "mengstack/internal/app/database" "mengstack/internal/app/health" "mengstack/internal/app/middleware" "mengstack/internal/config" "mengstack/internal/logger" authinterfaces "mengstack/internal/modules/auth/interfaces" rbacinterfaces "mengstack/internal/modules/rbac/interfaces" orginterfaces "mengstack/internal/modules/org/interfaces" auditinterfaces "mengstack/internal/modules/audit/interfaces" settingsinterfaces "mengstack/internal/modules/settings/interfaces" notificationinterfaces "mengstack/internal/modules/notification/interfaces" "github.com/gin-gonic/gin" "github.com/redis/go-redis/v9" "go.uber.org/fx" "go.uber.org/zap" "gorm.io/gorm" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" ) var Module = fx.Module("app", fx.Provide(newEngine), fx.Provide(newServer), fx.Invoke(registerLifecycle), ) func newEngine( cfg *config.Config, log *zap.Logger, db *gorm.DB, rdb *redis.Client, authHandler *authinterfaces.Handler, authMW gin.HandlerFunc, rbacHandler *rbacinterfaces.Handler, orgHandler *orginterfaces.Handler, auditHandler *auditinterfaces.Handler, settingsHandler *settingsinterfaces.Handler, notificationHandler *notificationinterfaces.Handler, ) *gin.Engine { ginMode := "release" if cfg.Server.Mode == "debug" || cfg.Server.Mode == "dev" { ginMode = "debug" } gin.SetMode(ginMode) r := gin.New() r.Use(middleware.RequestID()) r.Use(middleware.RequestLogger(log)) r.Use(middleware.SecurityHeaders()) r.Use(middleware.CORS()) r.Use(middleware.RateLimit(100, time.Minute)) r.Use(middleware.BodyLimit(10 << 20)) r.Use(middleware.Recovery(log)) healthHandler := health.NewHandler(db, rdb, log) r.GET("/health", healthHandler.Handle) r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) authinterfaces.SetupRoutes(r, authHandler, authMW) rbacinterfaces.SetupRoutes(r, rbacHandler, authMW) orginterfaces.SetupRoutes(r, orgHandler, authMW) auditinterfaces.SetupRoutes(r, auditHandler, authMW) settingsinterfaces.SetupRoutes(r, settingsHandler, authMW) notificationinterfaces.SetupRoutes(r, notificationHandler, authMW) return r } func newServer(cfg *config.Config, engine *gin.Engine) *http.Server { return &http.Server{ Addr: fmt.Sprintf(":%d", cfg.Server.Port), Handler: engine, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } } func registerLifecycle(lc fx.Lifecycle, srv *http.Server, log *zap.Logger) { lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { log.Info("server starting", zap.String("addr", srv.Addr)) go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Error("server error", zap.Error(err)) } }() return nil }, OnStop: func(ctx context.Context) error { shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() log.Info("server shutting down") return srv.Shutdown(shutdownCtx) }, }) } func NewApp() *fx.App { return fx.New( fx.Provide( func() (*config.Config, error) { return config.Load() }, func(cfg *config.Config) (*zap.Logger, error) { return logger.New(cfg.Log) }, ), database.Module, cache.Module, authinterfaces.Module, rbacinterfaces.Module, orginterfaces.Module, auditinterfaces.Module, settingsinterfaces.Module, notificationinterfaces.Module, Module, ) }