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/internal/handlers" "github.com/creativenoz/aurganize-v62/backend/internal/middleware" "github.com/creativenoz/aurganize-v62/backend/internal/repositories" "github.com/creativenoz/aurganize-v62/backend/internal/routes" "github.com/creativenoz/aurganize-v62/backend/internal/services" "github.com/creativenoz/aurganize-v62/backend/jobs" "github.com/creativenoz/aurganize-v62/backend/pkg/logger" "github.com/go-playground/validator/v10" "github.com/jmoiron/sqlx" "github.com/labstack/echo/v4" echomiddleware "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") // ========================================================================= // Database Connection // ========================================================================= log.Info(). Str("host", cfg.DatabaseDSN()) db, err := sqlx.Connect("postgres", cfg.DatabaseDSN()) if err != nil { log.Fatal(). Err(err). Str("dsn", cfg.DatabaseDSN()). Msg("failed to connect to database") } defer db.Close() db.SetMaxOpenConns(cfg.Database.MaxOpenConns) db.SetMaxIdleConns(cfg.Database.MaxIdleConns) db.SetConnMaxLifetime(cfg.Database.ConnMaxLifetime) if err := db.Ping(); err != nil { log.Fatal(). Err(err). Str("dsn", cfg.DatabaseDSN()). Msg("failed to ping database") } log.Info(). Str("host", cfg.Database.Host). Str("database", cfg.Database.DBName). Msg("database connected successfully") // ========================================================================= // Initialize Repositories // ========================================================================= userRepo := repositories.NewUserRepository(db) sessionRepo := repositories.NewSessionRepository(db) tenantRepo := repositories.NewTenantRepository(db) log.Info(). Msg("repositories initialized") // ========================================================================= // Initialize Handlers // ========================================================================= authService := services.NewAuthService(cfg, sessionRepo, userRepo) userService := services.NewUserService(userRepo) tenantService := services.NewTenantService(cfg, tenantRepo, userRepo, db) log.Info(). Msg("services initialized") // ========================================================================= // Initialize Handlers // ========================================================================= authHandler := handlers.NewAuthHandler(cfg, authService, userService) userHandler := handlers.NewUserRegisterHandler(cfg, authService, userService, tenantService) tenantHandler := handlers.NewTenantHanlder(tenantService) log.Info(). Msg("handlers initialized") // ========================================================================= // Initialize Middleware // ========================================================================= authMiddleware := middleware.NewAuthMiddleware(authService) globalrateLimitterMiddleware := middleware.NewRateLimiter(5, time.Minute) log.Info(). Msg("middleware initialized") // ========================================================================= // Background Jobs // ========================================================================= sessionCleanUpJob := jobs.NewSessionCleanUpJob(sessionRepo) go func() { sessionCleanUpJob.Start(context.Background()) }() log.Info(). Dur("interval", 12*time.Hour). Msg("session clean up job started") // ========================================================================= // Create Echo Instance // ========================================================================= e := echo.New() e.HideBanner = true e.HidePort = true e.Validator = &customValidator{validator: validator.New()} 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(echomiddleware.Recover()) // Middleware catches panic // Returns 500 Internal Server Error // Server keeps running // ------------------------------------------------------------------------- // Setting request ID middleware e.Use(echomiddleware.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(echomiddleware.LoggerWithConfig(echomiddleware.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.NewCORSMiddleware()) // Prevents malicious sites from calling your API // ---------------------------------------------------------------------- // Setting Security Headers middleware e.Use(echomiddleware.SecureWithConfig(echomiddleware.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(echomiddleware.Gzip()) // TODO : Rate Limiting middleware (planning to use redis for custom rate limiter) log.Info().Msg("Middleware configured") // ========================================================================= // Route Mapping // ========================================================================= 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 --- PIKAAA CHUUUUU", "timestamp": time.Now().UTC().Format(time.RFC3339), "version": "0.6.2", }) }) routes.SetUpRoutes( e, authHandler, userHandler, tenantHandler, authMiddleware, globalrateLimitterMiddleware, ) 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") } type customValidator struct { validator *validator.Validate } func (cv *customValidator) Validate(i interface{}) error { if err := cv.validator.Struct(i); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } return nil } // 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), }, }) } }