package main import ( "context" "errors" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/juanatsap/cv-site/internal/config" "github.com/juanatsap/cv-site/internal/handlers" "github.com/juanatsap/cv-site/internal/middleware" "github.com/juanatsap/cv-site/internal/templates" ) const version = "1.0.0" func main() { // Initialize logger log.SetFlags(log.LstdFlags | log.Lshortfile) log.Println("🚀 Starting CV Server v" + version) // Load configuration cfg := config.Load() log.Printf("✓ Configuration loaded (env: %s)", os.Getenv("GO_ENV")) // Initialize template manager templateMgr, err := templates.NewManager(&cfg.Template) if err != nil { log.Fatalf("❌ Failed to initialize templates: %v", err) } // Initialize handlers cvHandler := handlers.NewCVHandler(templateMgr) healthHandler := handlers.NewHealthHandler(version) // Setup router mux := http.NewServeMux() // Routes mux.HandleFunc("/", cvHandler.Home) mux.HandleFunc("/cv", cvHandler.CVContent) mux.HandleFunc("/export/pdf", cvHandler.ExportPDF) mux.HandleFunc("/health", healthHandler.Check) // Static files with cache control staticHandler := http.StripPrefix("/static/", http.FileServer(http.Dir("static"))) mux.Handle("/static/", cacheControl(staticHandler)) // Apply middleware chain handler := middleware.Recovery( middleware.Logger( middleware.SecurityHeaders(mux), ), ) // Create server with timeouts server := &http.Server{ Addr: ":" + cfg.Server.Port, Handler: handler, ReadTimeout: time.Duration(cfg.Server.ReadTimeout) * time.Second, WriteTimeout: time.Duration(cfg.Server.WriteTimeout) * time.Second, IdleTimeout: 120 * time.Second, } // Start server in goroutine serverErrors := make(chan error, 1) go func() { log.Printf("✓ Server listening on http://%s:%s", cfg.Server.Host, cfg.Server.Port) log.Printf("📄 English: http://%s:%s/?lang=en", cfg.Server.Host, cfg.Server.Port) log.Printf("📄 Spanish: http://%s:%s/?lang=es", cfg.Server.Host, cfg.Server.Port) log.Printf("❤️ Health: http://%s:%s/health", cfg.Server.Host, cfg.Server.Port) log.Println("Press Ctrl+C to shutdown") serverErrors <- server.ListenAndServe() }() // Setup graceful shutdown shutdown := make(chan os.Signal, 1) signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) // Wait for shutdown signal or server error select { case err := <-serverErrors: if !errors.Is(err, http.ErrServerClosed) { log.Fatalf("❌ Server error: %v", err) } case sig := <-shutdown: log.Printf("🛑 Shutdown signal received: %v", sig) // Create shutdown context with timeout ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Attempt graceful shutdown if err := server.Shutdown(ctx); err != nil { log.Printf("⚠️ Graceful shutdown failed, forcing: %v", err) if err := server.Close(); err != nil { log.Fatalf("❌ Failed to close server: %v", err) } } log.Println("✓ Server stopped gracefully") } } // cacheControl adds cache headers to static files func cacheControl(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Cache static files for 1 hour in development, 1 day in production maxAge := "3600" // 1 hour if os.Getenv("GO_ENV") == "production" { maxAge = "86400" // 1 day } w.Header().Set("Cache-Control", "public, max-age="+maxAge) h.ServeHTTP(w, r) }) }