123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- package app
- import (
- "context"
- "fmt"
- "net/http"
- "os"
- "os/signal"
- "strconv"
- "strings"
- "syscall"
- "time"
- "git.linuxforward.com/byom/byom-core/config"
- "git.linuxforward.com/byom/byom-core/handlers"
- "git.linuxforward.com/byom/byom-core/hook"
- "git.linuxforward.com/byom/byom-core/jwtutils"
- "git.linuxforward.com/byom/byom-core/smtp"
- "git.linuxforward.com/byom/byom-core/store"
- "github.com/gin-contrib/cors"
- "github.com/gin-gonic/gin"
- "github.com/sirupsen/logrus"
- "gorm.io/driver/sqlite"
- "gorm.io/gorm"
- )
- type App struct {
- entry *logrus.Entry
- cnf *config.Config
- ctx context.Context
- cancel context.CancelFunc
- dataStore *store.DataStore
- emailSvc *smtp.Service
- jwtSvc *jwtutils.Service
- hookSvc *hook.HookClient
- userHandler *handlers.UserHandler
- workspaceHandler *handlers.WorkspaceHandler
- profileHandler *handlers.ProfileHandler
- router *gin.Engine
- }
- type ServiceHandler interface {
- SetupRoutes(r *gin.Engine)
- Close() error
- }
- func NewApp(cnf *config.Config) (*App, error) {
- ctx, cancel := context.WithCancel(context.Background())
- rtr := gin.New()
- rtr.Use(gin.Recovery())
- rtr.Use(gin.Logger())
- config := cors.DefaultConfig()
- config.AllowOrigins = []string{"*"}
- config.AllowMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}
- config.AllowHeaders = []string{"Origin", "Content-Type", "Accept", "Authorization"}
- config.AllowCredentials = true
- config.ExposeHeaders = []string{"Content-Length"}
- rtr.Use(cors.New(config))
- //init GORM
- dbConn, err := initDBConn(cnf.Database)
- if err != nil {
- cancel()
- return nil, fmt.Errorf("init db: %w", err)
- }
- //init email service
- emailSvc := smtp.NewService(cnf.Smtp)
- //init jwt service
- jwtSvc := jwtutils.NewService(cnf.Jwt.JwtSecret)
- //init hook service
- hookSvc := hook.NewHookClient(cnf.Hook.BaseURL, cnf.Hook.Domain, cnf.Hook.SecretKey)
- //init core functions
- store := store.NewDataStore(dbConn)
- userHandler := handlers.NewUserHandler(store, emailSvc, jwtSvc, hookSvc)
- workspaceHandler := handlers.NewWorkspaceHandler(store)
- profileHandler := handlers.NewProfileHandler(store)
- //add routes
- addRoutes(rtr, jwtSvc, userHandler, workspaceHandler, profileHandler)
- app := &App{
- entry: logrus.WithField("component", "app"),
- cnf: cnf,
- ctx: ctx,
- cancel: cancel,
- router: rtr,
- dataStore: store,
- emailSvc: emailSvc,
- jwtSvc: jwtSvc,
- userHandler: userHandler,
- workspaceHandler: workspaceHandler,
- profileHandler: profileHandler,
- }
- return app, nil
- }
- func (a *App) Run() error {
- a.entry.Info("Starting server...")
- if len(a.cnf.Server.Opts) > 0 {
- a.router.Use(func(c *gin.Context) {
- for _, opt := range a.cnf.Server.Opts {
- parts := strings.Split(opt, ": ")
- if len(parts) == 2 {
- c.Header(parts[0], parts[1])
- }
- }
- c.Next()
- })
- }
- // Configure server
- srv := &http.Server{
- Addr: fmt.Sprintf(":%s", strconv.Itoa(a.cnf.Server.ListeningPort)),
- Handler: a.router,
- }
- // Start server
- go func() {
- var err error
- if a.cnf.Server.TlsConfig.Enabled {
- a.entry.Infof("Starting server on port %d 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 %d without TLS", a.cnf.Server.ListeningPort)
- err = srv.ListenAndServe()
- }
- if err != nil && err != http.ErrServerClosed {
- a.entry.WithError(err).Error("Server error")
- }
- }()
- // Graceful shutdown
- quit := make(chan os.Signal, 1)
- signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
- <-quit
- a.entry.Info("Stopping server...")
- ctxTimeout, cancelFunc := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancelFunc()
- err := srv.Shutdown(ctxTimeout)
- if err != nil {
- return fmt.Errorf("shutdown server: %w", err)
- }
- // Close all services
- if err := a.Close(); err != nil {
- a.entry.WithError(err).Error("Error during service cleanup")
- }
- a.entry.Info("Server stopped successfully")
- return nil
- }
- func initDBConn(cnf *config.Database) (*gorm.DB, error) {
- db, err := gorm.Open(sqlite.Open(cnf.Path), &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) Close() error {
- a.entry.Info("Closing application resources...")
- if err := a.dataStore.Close(); err != nil {
- a.entry.WithError(err).Error("Failed to close database connections")
- return err
- }
- if err := a.emailSvc.Close(); err != nil {
- a.entry.WithError(err).Error("Failed to close email service")
- }
- if err := a.hookSvc.Close(); err != nil {
- a.entry.WithError(err).Error("Failed to close hook service")
- }
- return nil
- }
|