server.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. package app
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "os"
  7. "os/signal"
  8. "strconv"
  9. "strings"
  10. "syscall"
  11. "time"
  12. "git.linuxforward.com/byom/byom-core/config"
  13. "git.linuxforward.com/byom/byom-core/handlers"
  14. "git.linuxforward.com/byom/byom-core/hook"
  15. "git.linuxforward.com/byom/byom-core/jwtutils"
  16. "git.linuxforward.com/byom/byom-core/smtp"
  17. "git.linuxforward.com/byom/byom-core/store"
  18. "github.com/gin-contrib/cors"
  19. "github.com/gin-gonic/gin"
  20. "github.com/sirupsen/logrus"
  21. "gorm.io/driver/sqlite"
  22. "gorm.io/gorm"
  23. )
  24. type App struct {
  25. entry *logrus.Entry
  26. cnf *config.Config
  27. ctx context.Context
  28. cancel context.CancelFunc
  29. dataStore *store.DataStore
  30. emailSvc *smtp.Service
  31. jwtSvc *jwtutils.Service
  32. hookSvc *hook.HookClient
  33. userHandler *handlers.UserHandler
  34. workspaceHandler *handlers.WorkspaceHandler
  35. profileHandler *handlers.ProfileHandler
  36. router *gin.Engine
  37. }
  38. type ServiceHandler interface {
  39. SetupRoutes(r *gin.Engine)
  40. Close() error
  41. }
  42. func NewApp(cnf *config.Config) (*App, error) {
  43. ctx, cancel := context.WithCancel(context.Background())
  44. rtr := gin.New()
  45. rtr.Use(gin.Recovery())
  46. rtr.Use(gin.Logger())
  47. config := cors.DefaultConfig()
  48. config.AllowOrigins = []string{"*"}
  49. config.AllowMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}
  50. config.AllowHeaders = []string{"Origin", "Content-Type", "Accept", "Authorization"}
  51. config.AllowCredentials = true
  52. config.ExposeHeaders = []string{"Content-Length"}
  53. rtr.Use(cors.New(config))
  54. //init GORM
  55. dbConn, err := initDBConn(cnf.Database)
  56. if err != nil {
  57. cancel()
  58. return nil, fmt.Errorf("init db: %w", err)
  59. }
  60. //init email service
  61. emailSvc := smtp.NewService(cnf.Smtp)
  62. //init jwt service
  63. jwtSvc := jwtutils.NewService(cnf.Jwt.JwtSecret)
  64. //init hook service
  65. hookSvc := hook.NewHookClient(cnf.Hook.BaseURL, cnf.Hook.Domain, cnf.Hook.SecretKey)
  66. //init core functions
  67. store := store.NewDataStore(dbConn)
  68. userHandler := handlers.NewUserHandler(store, emailSvc, jwtSvc, hookSvc)
  69. workspaceHandler := handlers.NewWorkspaceHandler(store)
  70. profileHandler := handlers.NewProfileHandler(store)
  71. //add routes
  72. addRoutes(rtr, jwtSvc, userHandler, workspaceHandler, profileHandler)
  73. app := &App{
  74. entry: logrus.WithField("component", "app"),
  75. cnf: cnf,
  76. ctx: ctx,
  77. cancel: cancel,
  78. router: rtr,
  79. dataStore: store,
  80. emailSvc: emailSvc,
  81. jwtSvc: jwtSvc,
  82. userHandler: userHandler,
  83. workspaceHandler: workspaceHandler,
  84. profileHandler: profileHandler,
  85. }
  86. return app, nil
  87. }
  88. func (a *App) Run() error {
  89. a.entry.Info("Starting server...")
  90. if len(a.cnf.Server.Opts) > 0 {
  91. a.router.Use(func(c *gin.Context) {
  92. for _, opt := range a.cnf.Server.Opts {
  93. parts := strings.Split(opt, ": ")
  94. if len(parts) == 2 {
  95. c.Header(parts[0], parts[1])
  96. }
  97. }
  98. c.Next()
  99. })
  100. }
  101. // Configure server
  102. srv := &http.Server{
  103. Addr: fmt.Sprintf(":%s", strconv.Itoa(a.cnf.Server.ListeningPort)),
  104. Handler: a.router,
  105. }
  106. // Start server
  107. go func() {
  108. var err error
  109. if a.cnf.Server.TlsConfig.Enabled {
  110. a.entry.Infof("Starting server on port %d with TLS", a.cnf.Server.ListeningPort)
  111. err = srv.ListenAndServeTLS(
  112. a.cnf.Server.TlsConfig.CertFile,
  113. a.cnf.Server.TlsConfig.KeyFile,
  114. )
  115. } else {
  116. a.entry.Infof("Starting server on port %d without TLS", a.cnf.Server.ListeningPort)
  117. err = srv.ListenAndServe()
  118. }
  119. if err != nil && err != http.ErrServerClosed {
  120. a.entry.WithError(err).Error("Server error")
  121. }
  122. }()
  123. // Graceful shutdown
  124. quit := make(chan os.Signal, 1)
  125. signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
  126. <-quit
  127. a.entry.Info("Stopping server...")
  128. ctxTimeout, cancelFunc := context.WithTimeout(context.Background(), 30*time.Second)
  129. defer cancelFunc()
  130. err := srv.Shutdown(ctxTimeout)
  131. if err != nil {
  132. return fmt.Errorf("shutdown server: %w", err)
  133. }
  134. // Close all services
  135. if err := a.Close(); err != nil {
  136. a.entry.WithError(err).Error("Error during service cleanup")
  137. }
  138. a.entry.Info("Server stopped successfully")
  139. return nil
  140. }
  141. func initDBConn(cnf *config.Database) (*gorm.DB, error) {
  142. db, err := gorm.Open(sqlite.Open(cnf.Path), &gorm.Config{})
  143. if err != nil {
  144. return nil, fmt.Errorf("database: %w", err)
  145. }
  146. //Tune the DB
  147. sqlDB, err := db.DB()
  148. if err != nil {
  149. return nil, fmt.Errorf("database: %w", err)
  150. }
  151. sqlDB.SetMaxOpenConns(cnf.MaxOpenConns)
  152. sqlDB.SetMaxIdleConns(cnf.MaxIdleConns)
  153. sqlDB.SetConnMaxLifetime(time.Duration(cnf.ConnMaxLifetime) * time.Second)
  154. for _, pragma := range cnf.Pragma {
  155. tx := db.Exec("PRAGMA " + pragma)
  156. if tx.Error != nil {
  157. return nil, fmt.Errorf("database: %w", tx.Error)
  158. }
  159. }
  160. return db, nil
  161. }
  162. func (a *App) Close() error {
  163. a.entry.Info("Closing application resources...")
  164. if err := a.dataStore.Close(); err != nil {
  165. a.entry.WithError(err).Error("Failed to close database connections")
  166. return err
  167. }
  168. if err := a.emailSvc.Close(); err != nil {
  169. a.entry.WithError(err).Error("Failed to close email service")
  170. }
  171. if err := a.hookSvc.Close(); err != nil {
  172. a.entry.WithError(err).Error("Failed to close hook service")
  173. }
  174. return nil
  175. }