diff --git a/cmd/birthdaybot/main.go b/cmd/birthdaybot/main.go index dbbe9cc..fbdb1c6 100644 --- a/cmd/birthdaybot/main.go +++ b/cmd/birthdaybot/main.go @@ -47,8 +47,16 @@ func main() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) - srv.Start() + if err := srv.Start(); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: cannot start server: %s\n", err) + os.Exit(1) + } + s := <-c srv.Logger.Info("received signal, stopping", "signal", s) - srv.Stop() + + if err := srv.Stop(); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: cannot stop server: %s\n", err) + os.Exit(1) + } } diff --git a/example-config.yml b/example-config.yml index 34d1a22..57b9ca2 100644 --- a/example-config.yml +++ b/example-config.yml @@ -1,4 +1,7 @@ --- +web: + port: 8080 + birthdays: file: birthdays.csv template: ./birthday_message.tmpl diff --git a/model/config.go b/model/config.go index a0adc7e..469cd4c 100644 --- a/model/config.go +++ b/model/config.go @@ -19,6 +19,7 @@ var ( type Config struct { Birthdays *BirthdaysConfig `yaml:"birthdays"` Logger *LoggerConfig `yaml:"logger"` + Web *WebConfig `yaml:"web"` TelegramNotifications *TelegramNotificationsConfig `yaml:"telegram_notifications"` } @@ -31,6 +32,10 @@ func (c *Config) IsValid() error { return fmt.Errorf("invalid logger config: %w", err) } + if err := c.Web.IsValid(); err != nil { + return fmt.Errorf("invalid web config: %w", err) + } + if c.TelegramNotifications != nil { if err := c.TelegramNotifications.IsValid(); err != nil { return fmt.Errorf("invalid telegram notifications config: %w", err) @@ -53,6 +58,12 @@ func (c *Config) SetDefaults() { c.Logger.SetDefaults() + if c.Web == nil { + c.Web = &WebConfig{} + } + + c.Web.SetDefaults() + if c.TelegramNotifications != nil { c.TelegramNotifications.SetDefaults() } @@ -99,6 +110,20 @@ func (lc *LoggerConfig) IsValid() error { return nil } +type WebConfig struct { + Port int `yaml:"port"` +} + +func (wc *WebConfig) SetDefaults() { + if wc.Port == 0 { + wc.Port = 8080 + } +} + +func (wc *WebConfig) IsValid() error { + return nil +} + type TelegramNotificationsConfig struct { BotToken string `yaml:"bot_token"` ChannelID string `yaml:"channel_id"` diff --git a/server/server.go b/server/server.go index 1c0e715..a54d5db 100644 --- a/server/server.go +++ b/server/server.go @@ -22,6 +22,7 @@ var ( type Server struct { Logger *log.Logger Config *model.Config + WebServer *WebServer workers []Worker birthdays []*model.Birthday notificationServices []notification.NotificationService @@ -107,23 +108,45 @@ func New(options ...Option) (*Server, error) { } } + if srv.WebServer == nil { + srv.Logger.Debug("creating web server") + + srv.WebServer = NewWebServer(srv) + } + return srv, nil } -func (s *Server) Start() { +func (s *Server) Start() error { s.Logger.Info("starting server") + + if err := s.WebServer.Start(); err != nil { + return fmt.Errorf("cannot start web server: %w", err) + } + for _, worker := range s.workers { worker.Start() } + s.Logger.Debug("server started", "workers", len(s.workers)) + + return nil } -func (s *Server) Stop() { +func (s *Server) Stop() error { s.Logger.Info("stopping server") + + if err := s.WebServer.Stop(); err != nil { + return fmt.Errorf("cannot stop web server: %w", err) + } + for _, worker := range s.workers { worker.Stop() } + s.Logger.Debug("server stopped", "workers", len(s.workers)) + + return nil } func (s *Server) Notify(birthday *model.Birthday) error { diff --git a/server/web.go b/server/web.go new file mode 100644 index 0000000..8c5c489 --- /dev/null +++ b/server/web.go @@ -0,0 +1,58 @@ +package server + +import ( + "errors" + "fmt" + "net/http" + + "github.com/charmbracelet/log" +) + +type WebServer struct { + server *Server + logger *log.Logger + httpServer *http.Server +} + +func NewWebServer(server *Server) *WebServer { + ws := &WebServer{ + server: server, + logger: server.Logger, + httpServer: &http.Server{ + Addr: fmt.Sprintf(":%d", server.Config.Web.Port), + }, + } + + mux := http.NewServeMux() + mux.HandleFunc("/health", ws.healthHandler) + + ws.httpServer.Handler = mux + + return ws +} + +func (ws *WebServer) Start() error { + ws.logger.Debug("starting web server") + + go func() { + if err := ws.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + ws.logger.Fatal("cannot start web server", "error", err) + } + }() + + return nil +} + +func (ws *WebServer) Stop() error { + ws.logger.Debug("stopping web server") + + if err := ws.httpServer.Close(); err != nil { + return fmt.Errorf("cannot stop web server: %w", err) + } + + return nil +} + +func (ws *WebServer) healthHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "OK") +}