package main import ( "fmt" "io" "log" "net/http" "os" "time" "github.com/gin-gonic/gin" "github.com/golang-migrate/migrate/v4" _ "github.com/golang-migrate/migrate/v4/database/postgres" _ "github.com/golang-migrate/migrate/v4/source/file" "ems-backend/db" "ems-backend/models" "ems-backend/routes" "ems-backend/services" "ems-backend/utils" ) func initSchema() { // Construct DB URL from env vars or use a default one suitable for the container environment // Expected format: postgres://user:password@host:port/dbname?sslmode=disable dbUser := os.Getenv("POSTGRES_USER") if dbUser == "" { dbUser = "ems" } dbPass := os.Getenv("DB_PASSWORD") // Note: inconsistent naming in env, checking plan if dbPass == "" { dbPass = "ems_pass" } // Fallback dbHost := os.Getenv("POSTGRES_HOST") if dbHost == "" { dbHost = "postgres" } dbPort := os.Getenv("POSTGRES_PORT") if dbPort == "" { dbPort = "5432" } dbName := os.Getenv("POSTGRES_DB") if dbName == "" { dbName = "ems" } // If DB_PASSWORD env var is not set but POSTGRES_PASSWORD is (from docker-compose) if dbPass == "ems_pass" && os.Getenv("POSTGRES_PASSWORD") != "" { dbPass = os.Getenv("POSTGRES_PASSWORD") } dbUrl := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", dbUser, dbPass, dbHost, dbPort, dbName) log.Printf("Initializing schema migration...") // Point to the migration files inside the container var m *migrate.Migrate var err error maxRetries := 30 for i := 0; i < maxRetries; i++ { m, err = migrate.New("file:///app/migrations", dbUrl) if err == nil { break } log.Printf("Migration initialization failed (attempt %d/%d): %v. Retrying in 2 seconds...", i+1, maxRetries, err) time.Sleep(2 * time.Second) } if err != nil { log.Printf("Migration initialization finally failed (might be in dev or path missing): %v", err) return } if err := m.Up(); err != nil && err != migrate.ErrNoChange { log.Fatal("Database migration failed:", err) } log.Println("Database schema is up to date.") } func main() { initSchema() models.InitDB() // --- Initialize Log Rotation --- logWriter := utils.NewDailyLogWriter("logs") // Output to both stdout and file multiWriter := io.MultiWriter(os.Stdout, logWriter) // Configure standard logger log.SetOutput(multiWriter) log.SetFlags(log.LstdFlags | log.Lshortfile) // Configure Gin logger gin.DefaultWriter = multiWriter // Initialize Time Series Database (TDengine) db.InitTDengine() // Start async TDengine writer when TDengine is configured (decouples collection from write) if db.TD != nil { services.GlobalTDengineWriter = services.NewTDengineWriter() services.GlobalTDengineWriter.Start() defer services.GlobalTDengineWriter.Stop() } // Initialize Backup Service services.InitBackupService() // Initialize Alarm Service services.NewAlarmService() // Start Data Collector Service collector := services.NewCollectorService() collector.Start() // Note: collector.Stop() is not strictly needed as container kill handles cleanup, // but can be added if we implement graceful shutdown. r := gin.Default() // Setup Routes routes.SetupRoutes(r) // Health Check r.GET("/ping", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "pong", "system": "EMS Backend", }) }) // API Group v1 := r.Group("/api/v1") { v1.GET("/info", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "version": "1.0.0", "env": os.Getenv("GIN_MODE"), }) }) } port := os.Getenv("PORT") if port == "" { port = "8080" } fmt.Printf("Server starting on port %s...\n", port) r.Run(":" + port) }