package server import ( "errors" "fmt" "path" "text/template" "time" "git.ctrlz.es/mgdelacroix/birthdaybot/model" "git.ctrlz.es/mgdelacroix/birthdaybot/notification" "git.ctrlz.es/mgdelacroix/birthdaybot/parser" "github.com/charmbracelet/log" ) var ( ErrNoNotificationServices = errors.New("there are no notification services configured") ErrNoLogger = errors.New("there is no logger configured") ErrNoConfig = errors.New("configuration is required to create a server") ) type Server struct { Logger *log.Logger Config *model.Config WebServer *WebServer workers []Worker birthdays []*model.Birthday notificationServices []notification.NotificationService tmpl *template.Template } func (s *Server) createNotificationServices() ([]notification.NotificationService, error) { notificationServices := []notification.NotificationService{} if s.Config.TelegramNotifications != nil { telegramNotificationService, err := notification.NewTelegramNotificationService(s.Logger, s.Config) if err != nil { return nil, fmt.Errorf("cannot create telegram notification service: %w", err) } notificationServices = append(notificationServices, telegramNotificationService) } if len(notificationServices) == 0 { return nil, ErrNoNotificationServices } return notificationServices, nil } func New(options ...Option) (*Server, error) { srv := &Server{} for _, option := range options { srv = option(srv) } if srv.Logger == nil { return nil, ErrNoLogger } if srv.Config == nil { return nil, ErrNoConfig } if len(srv.birthdays) == 0 { srv.Logger.Debug("parsing CSV file", "birthdayFile", srv.Config.Birthdays.File) var err error srv.birthdays, err = parser.ParseCSV(srv.Config.Birthdays.File) if err != nil { srv.Logger.Error("cannot parse CSV file", "birthdayFile", srv.Config.Birthdays.File, "error", err) return nil, fmt.Errorf("cannot parse CSV file %s: %w", srv.Config.Birthdays.File, err) } } if len(srv.notificationServices) == 0 { srv.Logger.Debug("creating notification services from config") var err error srv.notificationServices, err = srv.createNotificationServices() if err != nil { srv.Logger.Error("error creating notification services", "error", err) return nil, err } } if len(srv.workers) == 0 { srv.Logger.Debug("creating server workers") srv.workers = []Worker{NewSimpleWorker(srv)} } if srv.Config.Birthdays.Template != "" { srv.Logger.Debug("parsing birthday template", "file", srv.Config.Birthdays.Template) funcs := template.FuncMap{ "getYearsOld": func(yearOfBirth int) int { return time.Now().Year() - yearOfBirth }, } var err error srv.tmpl, err = template. New(path.Base(srv.Config.Birthdays.Template)). Funcs(funcs). ParseFiles(srv.Config.Birthdays.Template) if err != nil { return nil, fmt.Errorf("cannot parse template file %q: %w", srv.Config.Birthdays.Template, err) } } if srv.WebServer == nil && srv.Config.Web.Enabled { srv.Logger.Debug("creating web server") ws, err := NewWebServer(srv) if err != nil { return nil, fmt.Errorf("cannot create web server: %w", err) } srv.WebServer = ws } return srv, nil } func (s *Server) Start() error { s.Logger.Info("starting server") if s.WebServer != nil { 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() error { s.Logger.Info("stopping server") if s.WebServer != nil { 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 { errs := []error{} for _, service := range s.notificationServices { err := service.Notify(birthday, s.tmpl) if err != nil { errs = append(errs, err) } } if len(errs) == 0 { return nil } return errors.Join(errs...) } func (s *Server) Birthdays() []*model.Birthday { return s.birthdays } func (s *Server) NextBirthdays() []*model.Birthday { return model.NextBirthdays(s.birthdays, time.Now()) }