package main import ( "context" "fmt" "log" "log/slog" "net/http" "os" "os/signal" "time" "ibd-trader/internal/analyzer/openai" auth2 "ibd-trader/internal/auth" "ibd-trader/internal/config" "ibd-trader/internal/database" "ibd-trader/internal/ibd" "ibd-trader/internal/keys" "ibd-trader/internal/leader/election" "ibd-trader/internal/leader/manager" "ibd-trader/internal/server2" "ibd-trader/internal/worker" "github.com/lmittmann/tint" "github.com/redis/go-redis/v9" ) func main() { // Load the config cfg, err := config.New() if err != nil { log.Fatal("Unable to load config: ", err) } // Setup slog var level slog.Level if err = level.UnmarshalText([]byte(cfg.Log.Level)); err != nil { log.Fatal("Unable to parse log level: ", err) } var logger *slog.Logger opts := &tint.Options{ AddSource: cfg.Log.AddSource, Level: level, NoColor: !cfg.Log.Color, } logger = slog.New(tint.NewHandler(os.Stdout, opts)) slog.SetDefault(logger) logger.Info( "Starting IBD Trader...", "logger.level", level, ) // Connect to the database db, err := connectDB(logger, cfg) defer db.Close() if err != nil { log.Fatal("Unable to connect to database: ", err) } // Connect to redis redisClient := redis.NewClient(&redis.Options{ Addr: cfg.Redis.Addr, Password: cfg.Redis.Password, }) defer redisClient.Close() // Setup auth auth, err := auth2.New(cfg) if err != nil { log.Fatal("Unable to setup auth: ", err) } _ = auth // Setup IBD client client, err := ibd.NewClient(http.DefaultClient, cfg.IBD.APIKey, db, cfg.IBD.ProxyURL) if err != nil { log.Fatal("Unable to setup IBD client: ", err) } // Setup analyzer analyzer := openai.NewAnalyzer(openai.WithDefaultConfig(cfg.Analyzer.OpenAI.APIKey)) _ = analyzer // Setup context w/ signal handling ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() //// Start the server //go func() { // if err := server.StartServer(ctx, cfg, logger, db, auth, client, redisClient); err != nil { // log.Fatal("Unable to start server: ", err) // } // // Cancel the context when the server stops // cancel() //}() // Start the gRPC server go func() { server, err := server2.New(ctx, cfg.Server.Port, db, redisClient) if err != nil { log.Fatal("Unable to create gRPC server: ", err) } if err := server.Serve(ctx); err != nil { slog.ErrorContext(ctx, "Unable to start gRPC server", "error", err) } // Cancel the context when the server stops cancel() }() // Start the worker go func() { err := worker.StartWorker( ctx, client, redisClient, db, analyzer, ) if err != nil { log.Fatal("Unable to start worker: ", err) } // Cancel the context when the worker stops cancel() }() // Start leader election election.RunOrDie( ctx, redisClient, func(ctx context.Context) { m, err := manager.New(ctx, cfg, redisClient, db, client) if err != nil { logger.Error("Unable to create manager", "error", err) return } if err = m.Run(ctx); err != nil { logger.Error("Manager exited with error", "error", err) } }, ) } func connectDB(logger *slog.Logger, cfg *config.Config) (database.Database, error) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() kms, err := keys.NewGoogleKMS(ctx) if err != nil { return nil, fmt.Errorf("unable to create Google KMS Client: %w", err) } db, err := database.New(ctx, logger, cfg.DB.URL, kms, cfg.KMS.GCP.String()) if err != nil { return nil, err } return db, nil }