package main import ( "context" "net/http" "os" "os/signal" "strings" "syscall" "time" "gdelt-engine/internal/api" "gdelt-engine/internal/config" "gdelt-engine/internal/console" "gdelt-engine/internal/processor" "gdelt-engine/internal/storage" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) const version = "3.0.0" func main() { // Load configuration cfg := config.Load() // Initialize loggers zapLogger := initZapLogger(cfg.LogLevel) defer zapLogger.Sync() consoleLog := console.New(version) // Print startup banner consoleLog.PrintBanner(false) consoleLog.Info("Mode: Timestamp-triggered processing with parallel workers") // Debug: Check if MONGO_URI is set mongoURI := cfg.MongoURI if mongoURI == "" || mongoURI == "mongodb://localhost:27017" { consoleLog.Warn("MONGO_URI not set or using default - check your secrets!") } else { // Mask the URI for logging (show first 20 chars only) masked := mongoURI if len(masked) > 30 { masked = masked[:30] + "..." } consoleLog.Info("MONGO_URI detected: %s", masked) } // Create context with signal handling ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Handle graceful shutdown sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) // Connect to MongoDB consoleLog.Info("Connecting to MongoDB...") db, err := storage.NewMongoDB(ctx, cfg.MongoURI, cfg.DatabaseName, zapLogger) if err != nil { consoleLog.Fatal("MongoDB connection failed: %v", err) } defer func() { closeCtx, closeCancel := context.WithTimeout(context.Background(), 5*time.Second) defer closeCancel() db.Close(closeCtx) }() consoleLog.Success("MongoDB connected to %s", cfg.DatabaseName) // Create processor with worker pool proc := processor.NewProcessor(db, zapLogger, processor.WithMaxParallelTimestamps(24), processor.WithTimeout(5*time.Minute), processor.WithBatchSize(1000), ) consoleLog.Success("Processor initialized") // Create API handlers handlers := api.NewHandlers(proc, db.GetStats) // Create and start API server apiServer := api.NewServer(cfg.Port, handlers) go func() { consoleLog.Info("API server starting on :%s", cfg.Port) consoleLog.Info("Endpoints:") consoleLog.Info(" POST /process - Submit timestamps for processing") consoleLog.Info(" GET /status/{ts} - Check timestamp status") consoleLog.Info(" GET /timestamps - List all timestamps") consoleLog.Info(" GET /stats - Database statistics") consoleLog.Info(" GET /health - Health check") if err := apiServer.Start(); err != nil && err != http.ErrServerClosed { consoleLog.Error("API server error: %v", err) } }() consoleLog.Success("Ready - waiting for requests") // Wait for shutdown signal sig := <-sigCh consoleLog.Warn("Shutdown signal received: %s", sig.String()) // Graceful shutdown shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) defer shutdownCancel() if err := apiServer.Shutdown(shutdownCtx); err != nil { consoleLog.Error("API server shutdown error: %v", err) } cancel() consoleLog.Success("Shutdown complete") } func initZapLogger(level string) *zap.Logger { var zapLevel zapcore.Level switch strings.ToLower(level) { case "debug": zapLevel = zapcore.DebugLevel case "warn": zapLevel = zapcore.WarnLevel case "error": zapLevel = zapcore.ErrorLevel default: zapLevel = zapcore.InfoLevel } encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.TimeKey = "ts" encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder config := zap.Config{ Level: zap.NewAtomicLevelAt(zapLevel), Development: false, Encoding: "json", EncoderConfig: encoderConfig, OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, } logger, err := config.Build() if err != nil { panic(err) } return logger }