package app import ( "context" "fmt" "os" "os/signal" "syscall" "time" "github.com/gocql/gocql" "go.uber.org/zap" "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config" "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http" "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/scheduler" "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/scheduler/tasks" "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/storage/database/cassandradb" ) // Application represents the main application using Wire DI type Application struct { config *config.Config httpServer *http.WireServer logger *zap.Logger migrator *cassandradb.Migrator scheduler *scheduler.Scheduler ipAnonymizationTask *tasks.IPAnonymizationTask dbSession *gocql.Session } // ProvideApplication creates the application instance for Wire func ProvideApplication( cfg *config.Config, httpServer *http.WireServer, logger *zap.Logger, migrator *cassandradb.Migrator, sched *scheduler.Scheduler, ipAnonymizationTask *tasks.IPAnonymizationTask, dbSession *gocql.Session, ) *Application { return &Application{ config: cfg, httpServer: httpServer, logger: logger, migrator: migrator, scheduler: sched, ipAnonymizationTask: ipAnonymizationTask, dbSession: dbSession, } } // Start starts the application func (app *Application) Start() error { app.logger.Info("🚀 MapleFile Backend Starting (Wire DI)", zap.String("version", app.config.App.Version), zap.String("environment", app.config.App.Environment), zap.String("di_framework", "Google Wire")) // Run database migrations automatically on startup if enabled if app.config.Database.AutoMigrate { app.logger.Info("Auto-migration enabled, running database migrations...") if err := app.migrator.Up(); err != nil { app.logger.Error("Failed to run database migrations", zap.Error(err)) return fmt.Errorf("migration failed: %w", err) } app.logger.Info("✅ Database migrations completed successfully") // Wait for schema agreement across all Cassandra nodes // This ensures all nodes have the new schema before we start accepting requests app.logger.Info("⏳ Waiting for Cassandra schema agreement...") ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() if err := app.dbSession.AwaitSchemaAgreement(ctx); err != nil { app.logger.Warn("Schema agreement wait failed, continuing anyway", zap.Error(err), zap.String("note", "This may cause transient errors on first requests")) } else { app.logger.Info("✅ Cassandra schema agreement reached") } } else { app.logger.Info("Auto-migration disabled (DATABASE_AUTO_MIGRATE=false), skipping migrations") } // Register scheduled tasks app.logger.Info("Registering scheduled tasks...") if err := app.scheduler.RegisterTask(app.ipAnonymizationTask); err != nil { app.logger.Error("Failed to register IP anonymization task", zap.Error(err)) return fmt.Errorf("task registration failed: %w", err) } // Start scheduler if err := app.scheduler.Start(); err != nil { app.logger.Error("Failed to start scheduler", zap.Error(err)) return fmt.Errorf("scheduler startup failed: %w", err) } // Start HTTP server in goroutine errChan := make(chan error, 1) go func() { if err := app.httpServer.Start(); err != nil { errChan <- err } }() // Wait for interrupt signal or server error quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) select { case err := <-errChan: app.logger.Error("HTTP server failed", zap.Error(err)) return fmt.Errorf("server startup failed: %w", err) case sig := <-quit: app.logger.Info("Received shutdown signal", zap.String("signal", sig.String())) } app.logger.Info("👋 MapleFile Backend Shutting Down") // Graceful shutdown with timeout ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Stop scheduler first app.logger.Info("Stopping scheduler...") if err := app.scheduler.Stop(); err != nil { app.logger.Error("Scheduler shutdown error", zap.Error(err)) // Continue with shutdown even if scheduler fails } // Stop HTTP server if err := app.httpServer.Shutdown(ctx); err != nil { app.logger.Error("Server shutdown error", zap.Error(err)) return fmt.Errorf("server shutdown failed: %w", err) } app.logger.Info("✅ MapleFile Backend Stopped Successfully") return nil }