123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- package app
- import (
- "context"
- "fmt"
- "net/http"
- "os"
- "os/signal"
- "syscall"
- "time"
- "git.linuxforward.com/byom/byom-gateway/config"
- "git.linuxforward.com/byom/byom-gateway/handlers"
- "git.linuxforward.com/byom/byom-gateway/logger"
- "git.linuxforward.com/byom/byom-gateway/middleware"
- "git.linuxforward.com/byom/byom-gateway/store"
- "gorm.io/driver/sqlite"
- "gorm.io/gorm"
- "github.com/gin-contrib/cors"
- "github.com/gin-gonic/gin"
- "github.com/sirupsen/logrus"
- )
- type App struct {
- entry *logrus.Entry
- cnf *config.Config
- ctx context.Context
- cancel context.CancelFunc
- dbHandler *store.DataStore
- userHandler *handlers.UserHandler
- router *gin.Engine
- }
- func NewApp(cnf *config.Config) (*App, error) {
- ctx, cancel := context.WithCancel(context.Background())
- // Create logger
- entry := logger.NewLogger("app")
- // Create app instance
- app := &App{
- entry: entry,
- cnf: cnf,
- ctx: ctx,
- cancel: cancel,
- }
- // Initialize services
- if err := app.initServices(); err != nil {
- cancel()
- return nil, fmt.Errorf("failed to initialize services: %w", err)
- }
- // Initialize router and middleware
- if err := app.initRouter(); err != nil {
- cancel()
- return nil, fmt.Errorf("init router: %w", err)
- }
- return app, nil
- }
- func (a *App) initRouter() error {
- rtr := gin.Default()
- coreRtr := rtr.Group("/api/v1/gw")
- // Add core middleware
- coreRtr.Use(middleware.Recovery(a.entry.Logger))
- coreRtr.Use(middleware.RequestID())
- coreRtr.Use(middleware.RequestLogger(a.entry.Logger))
- coreRtr.Use(middleware.ErrorHandler())
- // Add security middleware
- coreRtr.Use(middleware.SecurityHeaders())
- coreRtr.Use(middleware.RequestSanitizer())
- // Configure request timeout
- timeout := 30 * time.Second
- coreRtr.Use(middleware.TimeoutMiddleware(timeout))
- //Configure CORS
- coreRtr.Use(cors.New(cors.Config{
- AllowOrigins: []string{"http://172.27.28.86:5173"},
- AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
- AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Request-ID"},
- ExposeHeaders: []string{"Content-Length", "Content-Type", "X-Request-ID", "Location"},
- AllowCredentials: true,
- }))
- // Add health check endpoint
- coreRtr.GET("/health", func(c *gin.Context) {
- c.JSON(200, gin.H{"status": "ok"})
- })
- // Add user routes
- // userGroup := rtr.Group("/user", gin.BasicAuth(gin.Accounts{
- // a.cnf.BasicAuth.Username: a.cnf.BasicAuth.Password,
- // }))
- userGroup := coreRtr.Group("/user")
- {
- userGroup.GET("/host", a.userHandler.GetUserHost)
- userGroup.POST("/host", a.userHandler.AddUserHost)
- userGroup.DELETE("/host", a.userHandler.DeleteUserHost)
- userGroup.PUT("/host", a.userHandler.UpdateUserHost)
- }
- a.router = rtr
- return nil
- }
- func (a *App) initServices() error {
- // Initialize database
- dbConn, err := initDBConn(&a.cnf.Database)
- if err != nil {
- return fmt.Errorf("init database: %w", err)
- }
- dbHandler := store.NewDataStore(dbConn)
- a.dbHandler = dbHandler
- // Initialize user handler
- userHandler := handlers.NewUserHandler(dbHandler, a.cnf.Homepage)
- a.userHandler = userHandler
- return nil
- }
- func initDBConn(cnf *config.DatabaseConfig) (*gorm.DB, error) {
- db, err := gorm.Open(sqlite.Open(cnf.File), &gorm.Config{})
- if err != nil {
- return nil, fmt.Errorf("database: %w", err)
- }
- //Tune the DB
- sqlDB, err := db.DB()
- if err != nil {
- return nil, fmt.Errorf("database: %w", err)
- }
- sqlDB.SetMaxOpenConns(cnf.MaxOpenConns)
- sqlDB.SetMaxIdleConns(cnf.MaxIdleConns)
- sqlDB.SetConnMaxLifetime(time.Duration(cnf.ConnMaxLifetime) * time.Second)
- for _, pragma := range cnf.Pragma {
- tx := db.Exec("PRAGMA " + pragma)
- if tx.Error != nil {
- return nil, fmt.Errorf("database: %w", tx.Error)
- }
- }
- return db, nil
- }
- func (a *App) Run() error {
- a.entry.Info("Starting server...")
- // Configure server with timeouts
- srv := &http.Server{
- Addr: ":" + a.cnf.Server.ListeningPort,
- Handler: a.router,
- ReadTimeout: 15 * time.Second,
- WriteTimeout: 15 * time.Second,
- IdleTimeout: 60 * time.Second,
- }
- // Start server
- go func() {
- var err error
- if a.cnf.Server.TlsConfig.Enabled {
- a.entry.Infof("Starting server on port %s with TLS", a.cnf.Server.ListeningPort)
- err = srv.ListenAndServeTLS(
- a.cnf.Server.TlsConfig.CertFile,
- a.cnf.Server.TlsConfig.KeyFile,
- )
- } else {
- a.entry.Infof("Starting server on port %s without TLS", a.cnf.Server.ListeningPort)
- err = srv.ListenAndServe()
- }
- if err != nil && err != http.ErrServerClosed {
- a.entry.WithError(err).Error("Server error")
- // Signal shutdown on critical error
- a.cancel()
- }
- }()
- // Graceful shutdown
- quit := make(chan os.Signal, 1)
- signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
- select {
- case <-quit:
- a.entry.Info("Shutdown signal received...")
- case <-a.ctx.Done():
- a.entry.Info("Server error occurred, initiating shutdown...")
- }
- // Create shutdown context with timeout
- ctxShutdown, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
- // Shutdown server
- if err := srv.Shutdown(ctxShutdown); err != nil {
- a.entry.WithError(err).Error("Server forced to shutdown")
- return fmt.Errorf("server forced to shutdown: %w", err)
- }
- // Close all services
- if err := a.Close(); err != nil {
- a.entry.WithError(err).Error("Error during service cleanup")
- return fmt.Errorf("service cleanup error: %w", err)
- }
- a.entry.Info("Server stopped gracefully")
- return nil
- }
- func (a *App) Close() error {
- if err := a.dbHandler.Close(); err != nil {
- return err
- }
- return nil
- }
|