package main import ( "os" "os/signal" "syscall" "context" "git.intra.yksa.space/gsn/predictor/internal/jobs/grib/updater" "git.intra.yksa.space/gsn/predictor/internal/pkg/errcodes" "git.intra.yksa.space/gsn/predictor/internal/pkg/grib" "git.intra.yksa.space/gsn/predictor/internal/pkg/log" "git.intra.yksa.space/gsn/predictor/internal/service" "git.intra.yksa.space/gsn/predictor/internal/transport/rest" "git.intra.yksa.space/gsn/predictor/internal/transport/rest/handler" "git.intra.yksa.space/gsn/predictor/pkg/redis" "git.intra.yksa.space/gsn/predictor/pkg/scheduler" env "github.com/caarlos0/env/v11" "go.uber.org/zap" ) const servicePrefix = "GSN_PREDICTOR" func main() { lg, err := zap.NewProduction() if err != nil { panic(err) } defer lg.Sync() ctx := log.ToCtx(context.Background(), lg) cfg, err := loadConfig() if err != nil { log.Ctx(ctx).Fatal("failed to load configuration", zap.Error(err)) } schedulerConfig, err := scheduler.NewConfig() if err != nil { log.Ctx(ctx).Fatal("failed to load scheduler configuration", zap.Error(err)) } gribUpdaterConfig, err := updater.NewConfig() if err != nil { log.Ctx(ctx).Fatal("failed to load GRIB updater configuration", zap.Error(err)) } log.Ctx(ctx).Info("Connecting to Redis", zap.String("host", cfg.RedisHost), zap.Int("port", cfg.RedisPort)) redisService, err := redis.New(redis.Config{ Host: cfg.RedisHost, Port: cfg.RedisPort, Password: cfg.RedisPassword, DB: cfg.RedisDB, }) if err != nil { log.Ctx(ctx).Fatal("failed to initialize Redis service", zap.Error(err), zap.String("host", cfg.RedisHost), zap.Int("port", cfg.RedisPort)) } defer redisService.Close() gribService, err := grib.New(grib.ServiceConfig{ Dir: cfg.GribDir, TTL: cfg.GribTTL, CacheTTL: cfg.GribCacheTTL, Redis: redisService, Parallel: cfg.GribParallel, Client: cfg.CreateHTTPClient(), DatasetURL: cfg.GribDatasetURL, }) if err != nil { log.Ctx(ctx).Fatal("failed to initialize GRIB service", zap.Error(err)) } defer gribService.Close() // Force GRIB update on startup in a goroutine go func() { log.Ctx(ctx).Info("Performing initial GRIB update (async)...") if err := gribService.Update(ctx); err != nil { log.Ctx(ctx).Error("initial GRIB update failed", zap.Error(err)) } else { log.Ctx(ctx).Info("initial GRIB update complete") } }() svc, err := service.New(cfg, gribService, redisService) if err != nil { log.Ctx(ctx).Fatal("failed to initialize service", zap.Error(err)) } defer svc.Close() var sched *scheduler.Scheduler if schedulerConfig.Enabled { sched = scheduler.New() gribJob := updater.New(gribService, gribUpdaterConfig) if err := sched.AddJob(gribJob); err != nil { log.Ctx(ctx).Error("failed to add GRIB update job to scheduler", zap.Error(err)) } log.Ctx(ctx).Info("scheduler initialized with jobs") } handler := handler.New(svc) restConfig, err := rest.NewConfig() if err != nil { lg.Fatal("failed to init transport config", zap.Error(err)) } transport, err := rest.New(handler, restConfig) if err != nil { lg.Fatal("failed to init transport", zap.Error(err)) } svc.Start() if sched != nil { sched.Start() lg.Info("scheduler started") } lg.Info("service started successfully", zap.String("grib_dir", cfg.GribDir), zap.Duration("grib_ttl", cfg.GribTTL), zap.Duration("grib_cache_ttl", cfg.GribCacheTTL), zap.Int("grib_parallel", cfg.GribParallel), zap.Bool("scheduler_enabled", schedulerConfig.Enabled), zap.Duration("grib_update_interval", gribUpdaterConfig.Interval)) sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) go func() { lg.Info("starting HTTP server on port", zap.Int("port", restConfig.Port)) transport.Run() }() <-sigChan lg.Info("received shutdown signal, stopping service") if sched != nil { sched.Stop() lg.Info("scheduler stopped") } } func loadConfig() (*service.Config, error) { cfg := &service.Config{} if err := env.ParseWithOptions(cfg, env.Options{ PrefixTagName: servicePrefix + "_", }); err != nil { return nil, errcodes.Wrap(err, "failed to parse configuration") } return cfg, nil }