package main import ( "context" "fmt" "log" "net/http" "os" "os/signal" "syscall" "time" "gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/rest" "gitea.dwysokinski.me/Kichiyaki/notificationarr/internal/service" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" ) const ( defaultPort = "9234" readTimeout = 2 * time.Second readHeaderTimeout = 2 * time.Second writeTimeout = 2 * time.Second idleTimeout = 2 * time.Second serverShutdownTimeout = 10 * time.Second ntfyDefaultUrl = "https://ntfy.sh" publisherDefaultTimeout = 5 * time.Second ) func main() { srv, err := newServer() if err != nil { log.Fatalln("newServer:", err) } go startServer(srv) log.Println("Server is listening on the port", defaultPort) waitForSignal(context.Background()) ctxShutdown, cancelShutdown := context.WithTimeout(context.Background(), serverShutdownTimeout) defer cancelShutdown() err = srv.Shutdown(ctxShutdown) if err != nil { log.Println("srv.Shutdown:", err) } } func newServer() (*http.Server, error) { router, err := newRouter() if err != nil { return nil, fmt.Errorf("newRouter: %w", err) } return &http.Server{ Addr: ":" + defaultPort, Handler: router, ReadTimeout: readTimeout, ReadHeaderTimeout: readHeaderTimeout, WriteTimeout: writeTimeout, IdleTimeout: idleTimeout, }, nil } func newRouter() (*chi.Mux, error) { sonarrSvc, err := newSonarrService() if err != nil { return nil, fmt.Errorf("newSonarrService: %w", err) } radarrSvc, err := newRadarrService() if err != nil { return nil, fmt.Errorf("newRadarrService: %w", err) } r := chi.NewRouter() r.Use( middleware.RealIP, middleware.RequestLogger(&middleware.DefaultLogFormatter{ NoColor: true, Logger: log.Default(), }), middleware.Recoverer, middleware.Heartbeat("/health"), ) rest.NewWebhookHandler(sonarrSvc, radarrSvc).Register(r) return r, nil } func newRadarrService() (*service.Radarr, error) { ntfy, err := newNtfyService("RADARR") if err != nil { return nil, fmt.Errorf("newNtfyService: %w", err) } return service.NewRadarr(ntfy), nil } func newSonarrService() (*service.Sonarr, error) { ntfy, err := newNtfyService("SONARR") if err != nil { return nil, fmt.Errorf("newNtfyService: %w", err) } return service.NewSonarr(ntfy), nil } func newNtfyService(envPrefix string) (*service.Ntfy, error) { url := os.Getenv(envPrefix + "_NTFY_URL") if url == "" { url = ntfyDefaultUrl } topic := os.Getenv(envPrefix + "_NTFY_TOPIC") if topic == "" { return nil, fmt.Errorf(`env "%s_NTFY_TOPIC" is required`, envPrefix) } timeout := os.Getenv(envPrefix + "_NTFY_REQ_TIMEOUT") parsedTimeout := publisherDefaultTimeout var err error if timeout != "" { parsedTimeout, err = time.ParseDuration(timeout) } if err != nil { return nil, fmt.Errorf(`os.Getenv("%s_NTFY_REQ_TIMEOUT"): %w`, envPrefix, err) } return service.NewNtfy( &http.Client{Timeout: parsedTimeout}, url, topic, os.Getenv(envPrefix+"_NTFY_USERNAME"), os.Getenv(envPrefix+"_NTFY_PASSWORD"), ), nil } func startServer(srv *http.Server) { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalln("srv.ListenAndServe:", err) } } func waitForSignal(ctx context.Context) { ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) defer stop() <-ctx.Done() }