package main import ( "context" "net/http" "os" "os/signal" "syscall" "tercul/internal/app" "tercul/internal/platform/config" "tercul/internal/platform/log" "time" "github.com/99designs/gqlgen/graphql/playground" "github.com/hibiken/asynq" graph "tercul/internal/adapters/graphql" "tercul/internal/platform/auth" ) // main is the entry point for the Tercul application. // It uses the ApplicationBuilder and ServerFactory to initialize all components // and start the servers in a clean, maintainable way. func main() { // Load configuration from environment variables config.LoadConfig() // Initialize structured logger with appropriate log level log.SetDefaultLevel(log.InfoLevel) log.LogInfo("Starting Tercul application", log.F("environment", config.Cfg.Environment), log.F("version", "1.0.0")) // Build application components appBuilder := app.NewApplicationBuilder() if err := appBuilder.Build(); err != nil { log.LogFatal("Failed to build application", log.F("error", err)) } defer appBuilder.Close() // Create server factory serverFactory := app.NewServerFactory(appBuilder) // Create servers backgroundServers, err := serverFactory.CreateBackgroundJobServers() if err != nil { log.LogFatal("Failed to create background job servers", log.F("error", err)) } // Create GraphQL server resolver := &graph.Resolver{ WorkRepo: appBuilder.GetRepositories().WorkRepository, UserRepo: appBuilder.GetRepositories().UserRepository, AuthorRepo: appBuilder.GetRepositories().AuthorRepository, TranslationRepo: appBuilder.GetRepositories().TranslationRepository, CommentRepo: appBuilder.GetRepositories().CommentRepository, LikeRepo: appBuilder.GetRepositories().LikeRepository, BookmarkRepo: appBuilder.GetRepositories().BookmarkRepository, CollectionRepo: appBuilder.GetRepositories().CollectionRepository, TagRepo: appBuilder.GetRepositories().TagRepository, CategoryRepo: appBuilder.GetRepositories().CategoryRepository, WorkService: appBuilder.GetServices().WorkService, Localization: appBuilder.GetServices().LocalizationService, AuthService: appBuilder.GetServices().AuthService, } jwtManager := auth.NewJWTManager() srv := graph.NewServerWithAuth(resolver, jwtManager) graphQLServer := &http.Server{ Addr: config.Cfg.ServerPort, Handler: srv, } log.LogInfo("GraphQL server created successfully", log.F("port", config.Cfg.ServerPort)) // Create GraphQL playground playgroundHandler := playground.Handler("GraphQL", "/query") playgroundServer := &http.Server{ Addr: config.Cfg.PlaygroundPort, Handler: playgroundHandler, } log.LogInfo("GraphQL playground created successfully", log.F("port", config.Cfg.PlaygroundPort)) // Start HTTP servers in goroutines go func() { log.LogInfo("Starting GraphQL server", log.F("port", config.Cfg.ServerPort)) if err := graphQLServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.LogFatal("Failed to start GraphQL server", log.F("error", err)) } }() go func() { log.LogInfo("Starting GraphQL playground", log.F("port", config.Cfg.PlaygroundPort)) if err := playgroundServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.LogFatal("Failed to start GraphQL playground", log.F("error", err)) } }() // Start background job servers in goroutines for i, server := range backgroundServers { go func(serverIndex int, srv *asynq.Server) { log.LogInfo("Starting background job server", log.F("serverIndex", serverIndex)) if err := srv.Run(asynq.NewServeMux()); err != nil { log.LogError("Background job server failed", log.F("serverIndex", serverIndex), log.F("error", err)) } }(i, server) } // Wait for interrupt signal to gracefully shutdown the servers quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.LogInfo("Shutting down servers...") // Graceful shutdown ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := graphQLServer.Shutdown(ctx); err != nil { log.LogError("GraphQL server forced to shutdown", log.F("error", err)) } if err := playgroundServer.Shutdown(ctx); err != nil { log.LogError("GraphQL playground forced to shutdown", log.F("error", err)) } // Shutdown background job servers for i, server := range backgroundServers { server.Shutdown() log.LogInfo("Background job server shutdown", log.F("serverIndex", i)) } log.LogInfo("All servers shutdown successfully") }