package main import ( "context" "fmt" "net/http" "os" "os/signal" "syscall" "time" "github.com/creativenoz/aurganize-v62/backend/internal/config" "github.com/creativenoz/aurganize-v62/backend/pkg/logger" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/rs/zerolog/log" ) func main() { // ========================================================================= // Loading Configuration // ========================================================================= cfg, err := config.Load() if err != nil { // we are not using logger here, since we need config information to set the log level fmt.Fprintf(os.Stderr, "Failed to load configurations : %v\n", err) // hence when config load fails we exit application, cause have not point in continuing further os.Exit(1) } // ========================================================================= // Initializing Logger // ========================================================================= logger.Init(cfg.Server.Environment) log.Info(). Str("Version", "0.6.2"). Str("environment", cfg.Server.Environment). Msg("Starting Aurganize v6.2 API server") // ========================================================================= // Create Echo Instance // ========================================================================= e := echo.New() e.HideBanner = true e.HidePort = true e.HTTPErrorHandler = customHTTPErrorHandler // we are using a custom error handler e.Server.ReadTimeout = cfg.Server.ReadTimeout e.Server.WriteTimeout = cfg.Server.WriteTimeout log.Info().Msg("Echo server instance created") // ========================================================================= // Middleware Pipeline // ========================================================================= // Setting safe recover middleware e.Use(middleware.Recover()) // Middleware catches panic // Returns 500 Internal Server Error // Server keeps running // ------------------------------------------------------------------------- // Setting request ID middleware e.Use(middleware.RequestID()) // Trace request through entire system // Link frontend error to backend logs // This adds a header : X-Request-ID: abc-123-def-456 // ------------------------------------------------------------------------ // Setting Logger format e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ Format: `{"time":"${time_rfc3339}","method":"${method}","uri":"${uri}",` + `"status":${status},"latency_ms":${latency_ms},"error":"${error}"}` + "\n", Output: log.Logger, })) // We are setting a custom log format, which is consisten with our logger format // { // "time": "2025-11-26T10:30:45Z", // "method": "POST", // "uri": "/api/v1/login", // "status": 200, // "latency_ms": 45, // "error": "" // } // ----------------------------------------------------------------------- // Setting CORS (Cross-Origin Resource Sharing) middleware e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ AllowOrigins: []string{ "http://localhost:5173", // (Development) Svelte dev server : this is the port suggested to be used with front-end "http://localhost:3000", // (Developement) Alternative dev port : this is an alternative port kept aside "https://app.aurganize.com", // (Production) Production frontend : we can use this subdomain itself for front-end }, AllowMethods: []string{ http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch, http.MethodOptions, }, AllowHeaders: []string{ "Origin", "Content-Type", "Accept", "Authorization", "X-Request-ID", }, AllowCredentials: true, // Not sure about why are using this option MaxAge: 3600, // 1 hour in seconds })) // Prevents malicious sites from calling your API // ---------------------------------------------------------------------- // Setting Security Headers middleware e.Use(middleware.SecureWithConfig(middleware.SecureConfig{ XSSProtection: "1; mode=block", ContentTypeNosniff: "nosniff", XFrameOptions: "SAMEORIGIN", HSTSMaxAge: 31536000, HSTSExcludeSubdomains: false, ContentSecurityPolicy: "default-src 'self'", })) // X-XSS-Protection: // - Blocks cross-site scripting attacks // - Browser detects XSS and blocks page // X-Content-Type-Options: nosniff: // - Prevents MIME-type sniffing // - Browser trusts Content-Type header // - Prevents executing scripts as HTML // X-Frame-Options: SAMEORIGIN: // - Prevents clickjacking // - Page can't be embedded in iframe (except same origin) // - Protects against UI redress attacks // Strict-Transport-Security (HSTS): // - Forces HTTPS for 1 year // - Prevents downgrade attacks // - Can't be disabled by user // Content-Security-Policy: // - Only load resources from same origin // - Prevents loading malicious scripts // - Additional layer of XSS protection // ------------------------------------------------------------------- // Setting Gzip compression middleware e.Use(middleware.Gzip()) // TODO : Rate Limiting middleware (planning to use redis for custom rate limiter) log.Info().Msg("Middleware configured") // ========================================================================= // Middleware Pipeline // ========================================================================= e.GET("/health", healthCheckHandler(cfg)) // (Public - health check) api := e.Group("/api/v6.2") api.GET("/ping", func(c echo.Context) error { // (Public - connectivity test) return c.JSON(http.StatusOK, map[string]string{ "message": "pika pikaaa", "timestamp": time.Now().UTC().Format(time.RFC3339), "version": "0.6.2", }) }) log.Info().Msg("Routes configured") // ========================================================================= // Start Server in a new thread // ========================================================================= serverAddr := fmt.Sprintf(":%s", cfg.Server.Port) go func() { log.Info(). Str("address", serverAddr). Str("environment", cfg.Server.Environment). Msg("Server starting") if err := e.Start(serverAddr); err != nil && err != http.ErrServerClosed { log.Fatal(). Err(err). Msg("Failed to start server") } }() // ========================================================================= // Shutdown Logic // ========================================================================= quit := make(chan os.Signal, 1) // Creates a channel named quit that can carry values of type os.Signal. // The 1 means it’s a buffered channel with capacity 1 → it can hold one signal without blocking. // This channel will be used to receive OS signals like Ctrl+C or kill. signal.Notify(quit, os.Interrupt, syscall.SIGTERM) // Whenever the process receives any of these signals, send them into the quit channel.” // os.Interrupt → typically the signal sent when you press Ctrl+C in the terminal. // syscall.SIGTERM → the “please terminate” signal, used by process managers / Docker / Kubernetes, <-quit // Blocks until we get a signal in this channel // This is a receive operation on the channel. // The code blocks here and does nothing until: // the OS sends os.Interrupt or SIGTERM, // which signal.Notify pushes into quit. // When a signal arrives, <-quit unblocks and the program continues. log.Info().Msg("Shutting down server gracefully..") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // Creates a context with timeout of 10 seconds. // This context is passed to e.Shutdown(ctx) // Echo will have at most 10 seconds to shut down gracefully. // After 10 seconds, the context is cancelled, and shutdown will be forced. if err := e.Shutdown(ctx); err != nil { log.Error(). Err(err). Msg("Server forced to shutdown, graceful shutdown failed") } // Calls Echo’s Shutdown method with your timeout context. // e.Shutdown(ctx): // -- stops accepting new requests, // -- waits for in-flight requests to finish, // -- closes the server gracefully (within the timeout). // If something goes wrong (e.g., it can’t shut down in time), err is non-nil: // -- logs an error saying graceful shutdown failed and it had to force close. log.Info().Msg("API Server exited") } // HealthCheck Handler function // This endpoint is to be used by: // - Load balancers to determine if instance is healthy // - Kubernetes for liveness and readiness probes // - Monitoring systems for uptime checks func healthCheckHandler(c *config.Config) echo.HandlerFunc { return func(e echo.Context) error { // TODO : we need to add health check for // - Database connection // - Redis connection if we are using it // - NATS connection // For now we are just returning OK, since most of the service dependecies are not implemented yet. response := map[string]interface{}{ "status": "healthy", "version": "0.6.2", "environment": c.Server.Environment, "timestamp": time.Now().UTC(), "checks": map[string]string{ "server": "ok", "database": "not setup", "redis": "not setup", "nats": "not setup", }, "uptime": 999999999999, // logic yet to be implemented } return e.JSON(http.StatusOK, response) } } // CustomHTTPErrorHandler // Used to provide consistent error response // Converts Echo erros to JSON format, that can be parsed at frontend func customHTTPErrorHandler(err error, c echo.Context) { // Setting default values code := http.StatusInternalServerError message := "internal server error" // Checking if the error is and Echo error // We do this through a type assertion err.(*echo.HTTPError) [ basetype.(type to assert to)] if he, ok := err.(*echo.HTTPError); ok { code = he.Code // Then we again check with a type assertion if msg, ok := he.Message.(string); ok { message = msg } } log.Debug(). Err(err). Int("status", code). Str("method", c.Request().Method). Str("path", c.Request().URL.Path). Msg("HTTP error") if code >= 500 { log.Error(). Err(err). Int("status", code). Str("method", c.Request().Method). Str("path", c.Request().URL.Path). Msg("HTTP error") } // If the response is not already written // Then we don't have to again log if !c.Response().Committed { c.JSON(code, map[string]interface{}{ "error": map[string]interface{}{ "code": code, "message": message, "timestamp": time.Now().UTC(), "path": c.Request().URL.Path, "request_id": c.Response().Header().Get(echo.HeaderXRequestID), }, }) } }