package serverimp import ( "context" "errors" "net/http" "os" "time" "github.com/go-chi/chi/v5" "github.com/tech/sendico/notification/interface/api" "github.com/tech/sendico/notification/interface/api/localizer" apiimip "github.com/tech/sendico/notification/internal/api" "github.com/tech/sendico/pkg/db" "github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/mservice" mduration "github.com/tech/sendico/pkg/mutil/duration" "go.uber.org/zap" "gopkg.in/yaml.v3" ) type httpServerConf struct { ListenAddress string `yaml:"listen_address"` ReadHeaderTimeout int `yaml:"read_header_timeout"` ShutdownTimeout int `yaml:"shutdown_timeout"` } // Config represents the server configuration type Config struct { API *api.Config `yaml:"api"` DB *db.Config `yaml:"database"` Localizer *localizer.Config `yaml:"localizer"` HTTPServer *httpServerConf `yaml:"http_server"` } // Instance represents an instance of the server type Imp struct { logger mlogger.Logger api mservice.MicroService config *Config db db.Factory l localizer.Localizer httpServer *http.Server debug bool file string } // Shutdown stops the server func (i *Imp) Shutdown() { // Shutdown HTTP server ctx, cancel := context.WithTimeout(context.Background(), mduration.Param2Duration(i.config.HTTPServer.ShutdownTimeout, time.Second)) i.logger.Info("Shutting HTTP server down...") if err := i.httpServer.Shutdown(ctx); err != nil { if !errors.Is(err, http.ErrServerClosed) { i.logger.Warn("Failed to shutdown HTTP server gracefully", zap.Error(err)) cancel() os.Exit(1) } } cancel() } func (i *Imp) Run() error { if err := i.httpServer.ListenAndServe(); err != nil { if !errors.Is(err, http.ErrServerClosed) { i.logger.Error("HTTP Server stopped unexpectedly", zap.Error(err)) } } i.logger.Info("HTTP Server stopped") if err := i.api.Finish(context.Background()); err != nil { i.logger.Warn("Error when finishing service", zap.Error(err)) } i.db.CloseConnection() return nil } // Start starts the server func (i *Imp) Start() error { i.logger.Info("Starting...", zap.String("config_file", i.file), zap.Bool("debug_mode", i.debug)) // Load configuration file data, err := os.ReadFile(i.file) if err != nil { i.logger.Error("Could not load configuration", zap.Error(err), zap.String("config_file", i.file)) return err } if err = yaml.Unmarshal(data, &i.config); err != nil { i.logger.Error("Failed to parse configuration", zap.Error(err)) return err } if i.db, err = db.NewConnection(i.logger, i.config.DB); err != nil { i.logger.Error("Could not open database connection", zap.Error(err)) return err } if i.l, err = localizer.CreateLocalizer(i.logger, i.config.Localizer); err != nil { i.logger.Error("Failed to create localizer", zap.Error(err)) return err } router := chi.NewRouter() if i.api, err = apiimip.CreateAPI(i.logger, i.config.API, i.l, i.db, router, i.debug); err != nil { i.logger.Error("Failed to create API instance", zap.Error(err)) return err } // Startup the HTTP Server in a way that we can gracefully shut it down again i.httpServer = &http.Server{ Addr: i.config.HTTPServer.ListenAddress, Handler: router, ReadHeaderTimeout: mduration.Param2Duration(i.config.HTTPServer.ReadHeaderTimeout, time.Second), } return i.Run() } func Create(logger mlogger.Logger, file string, debug bool) (*Imp, error) { srv := &Imp{ logger: logger, debug: debug, file: file, } return srv, nil }