mengstack-api/internal/app/app.go
MengStack Dev df809f1045
Some checks failed
CI / Build & Test (push) Failing after 1m31s
feat: user CRUD API + dashboard stats + kernel infrastructure
- Add user management endpoints (list/create/get/update/delete) with pagination and search
- Add dashboard stats endpoint with tenant/user/online counts and growth metrics
- Add tenant resolver middleware for multi-tenant request scoping
- Add i18n kernel with zh/en message files and AcceptLanguage middleware
- Add WebSocket hub/handler for real-time communication
- Add job scheduler kernel with cron support
- Add plugin sandbox for isolated execution
- Add storage kernel (local filesystem)
- Add event bus kernel for pub/sub
- Add cache kernel abstraction
- Add database migration runner and version upgrade checker
- Add rate limiting middleware with Redis backend
- Add SQL migrations for rbac, audit_logs, settings, notifications, examples
- Extend user repository with list/delete/count operations
- Register all module routes with tenant resolver
2026-10-03 03:42:58 +08:00

200 lines
5.8 KiB
Go

package app
import (
"context"
"fmt"
"net/http"
"time"
"mengstack/internal/app/cache"
"mengstack/internal/app/database"
"mengstack/internal/app/health"
"mengstack/internal/app/middleware"
appmigrate "mengstack/internal/app/migrate"
"mengstack/internal/app/upgrade"
"mengstack/internal/config"
"mengstack/internal/kernel/i18n"
"mengstack/internal/kernel/plugin"
"mengstack/internal/kernel/jobs"
"mengstack/internal/kernel/tenant"
kws "mengstack/internal/kernel/websocket"
"mengstack/internal/logger"
"mengstack/migrations"
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"
dashboardinterfaces "mengstack/internal/modules/dashboard/interfaces"
exampleinterfaces "mengstack/internal/modules/example/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,
newServer,
func(log *zap.Logger) *kws.Hub {
return kws.NewHub(log)
},
func(hub *kws.Hub, log *zap.Logger) *kws.Handler {
return kws.NewHandler(hub, log)
},
func(log *zap.Logger) (*jobs.Scheduler, error) {
return jobs.NewScheduler(log)
},
),
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,
dashboardHandler *dashboardinterfaces.Handler,
examplePlugin *exampleinterfaces.ExamplePlugin,
wsHandler *kws.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.AcceptLanguage())
r.Use(middleware.RateLimitRedis(rdb))
r.Use(middleware.BodyLimit(10 << 20))
r.Use(middleware.Recovery(log))
healthHandler := health.NewHandler(db, rdb, log)
r.GET("/health", healthHandler.Handle)
r.GET("/healthz", healthHandler.Healthz)
r.GET("/readyz", healthHandler.Readyz)
r.GET("/api/version", healthHandler.Version)
upgradeHandler := upgrade.NewHandler()
r.GET("/api/upgrade/check", upgradeHandler.Check)
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
tenantResolver := tenant.NewResolver(cfg.Tenant)
authinterfaces.SetupRoutes(r, authHandler, authMW, tenantResolver)
rbacinterfaces.SetupRoutes(r, rbacHandler, authMW, tenantResolver)
orginterfaces.SetupRoutes(r, orgHandler, authMW, tenantResolver)
auditinterfaces.SetupRoutes(r, auditHandler, authMW, tenantResolver)
settingsinterfaces.SetupRoutes(r, settingsHandler, authMW, tenantResolver)
notificationinterfaces.SetupRoutes(r, notificationHandler, authMW, tenantResolver)
dashboardinterfaces.SetupRoutes(r, dashboardHandler, authMW, tenantResolver)
r.GET("/api/v1/ws", authMW, wsHandler.ServeWS)
examplePlugin.SetupRoutes(r, authMW, tenantResolver)
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, db *gorm.DB, sched *jobs.Scheduler, log *zap.Logger) {
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
if err := i18n.Init(i18n.Files, "zh"); err != nil {
return fmt.Errorf("init i18n: %w", err)
}
log.Info("i18n initialized")
if err := appmigrate.Run(db, migrations.Files); err != nil {
return fmt.Errorf("run migrations: %w", err)
}
log.Info("database migrations applied")
for _, p := range plugin.All() {
meta := p.Metadata()
pfs := p.MigrationsFS()
if pfs == nil {
continue
}
if err := appmigrate.RunPlugin(db, meta.Name, pfs); err != nil {
return fmt.Errorf("plugin %s migrations: %w", meta.Name, err)
}
log.Info("plugin migrations applied", zap.String("plugin", meta.Name))
}
sandbox := plugin.NewSandbox(log)
if err := plugin.InitAll(log, sandbox); err != nil {
return err
}
sched.Start()
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")
if err := sched.Stop(); err != nil {
log.Error("scheduler stop error", zap.Error(err))
}
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,
dashboardinterfaces.Module,
exampleinterfaces.Module,
Module,
)
}