package main import ( "context" "fmt" "net/http" "os" "os/signal" "syscall" "time" "github.com/go-playground/validator/v10" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "tm/internal/domain" "tm/internal/handler" "tm/internal/infrastructure" "tm/internal/repository" "tm/internal/service" "tm/pkg/logger" customMiddleware "tm/pkg/middleware" "tm/pkg/response" ) func main() { // Load configuration config, err := infrastructure.LoadConfig("./configs") if err != nil { panic(fmt.Sprintf("Failed to load config: %v", err)) } // Initialize logger logger.InitLogger(&config.Logging) log := logger.GetLogger() log.Info("Starting Tender Management API Server", map[string]interface{}{ "version": "1.0.0", "port": config.Server.Port, }) // Initialize MongoDB connection ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(config.Database.MongoDB.URI)) if err != nil { log.Fatal("Failed to connect to MongoDB", map[string]interface{}{ "error": err.Error(), }) } // Ping MongoDB to verify connection if err = mongoClient.Ping(ctx, nil); err != nil { log.Fatal("Failed to ping MongoDB", map[string]interface{}{ "error": err.Error(), }) } log.Info("Connected to MongoDB successfully", map[string]interface{}{ "database": config.Database.MongoDB.Name, }) // Get MongoDB database db := mongoClient.Database(config.Database.MongoDB.Name) // Initialize validator validate := validator.New() // Initialize repositories customerRepo := repository.NewCustomerRepository(db, log) userRepo := repository.NewUserRepository(db, log) companyRepo := repository.NewCompanyRepository(db, log) // Initialize services customerAuthService := service.NewCustomerAuthService(customerRepo, companyRepo, &config.Auth, log) userAuthService := service.NewUserAuthService(userRepo, &config.Auth, log) // TODO: Initialize customer service when needed for admin customer management // customerService := service.NewCustomerService(customerRepo, log) // Backward compatibility - create generic auth service authService := handler.NewAuthHandler(customerAuthService, userAuthService, validate, log) // Initialize handlers customerAuthHandler := handler.NewCustomerAuthHandler(customerAuthService, validate, log) userAuthHandler := handler.NewUserAuthHandler(userAuthService, validate, log) // Initialize Echo e := echo.New() // Configure Echo e.HideBanner = true e.HidePort = true // Add built-in middleware e.Use(middleware.Logger()) e.Use(middleware.Recover()) e.Use(middleware.CORS()) e.Use(middleware.RequestID()) // Add custom middleware for logging e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { start := time.Now() err := next(c) log.Info("HTTP Request", map[string]interface{}{ "method": c.Request().Method, "path": c.Request().URL.Path, "status": c.Response().Status, "duration": time.Since(start).String(), "request_id": c.Response().Header().Get(echo.HeaderXRequestID), }) return err } }) // Add authentication middleware e.Use(customMiddleware.AuthMiddleware(customerAuthService, userAuthService, log)) // Setup routes setupRoutes(e, authService, customerAuthHandler, userAuthHandler) // Health check endpoint e.GET("/health", func(c echo.Context) error { return response.Success(c, map[string]interface{}{ "status": "healthy", "timestamp": time.Now().UTC(), "version": "1.0.0", }, "Service is healthy") }) // Start server in a goroutine go func() { address := fmt.Sprintf("%s:%d", config.Server.Host, config.Server.Port) log.Info("Server starting", map[string]interface{}{ "address": address, }) if err := e.Start(address); err != nil && err != http.ErrServerClosed { log.Fatal("Failed to start server", map[string]interface{}{ "error": err.Error(), }) } }() // Wait for interrupt signal to gracefully shutdown the server quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt, syscall.SIGTERM) <-quit log.Info("Server is shutting down...", nil) // Create a deadline for graceful shutdown ctx, cancel = context.WithTimeout(context.Background(), config.Server.Timeout) defer cancel() // Gracefully shutdown the server if err := e.Shutdown(ctx); err != nil { log.Fatal("Server forced to shutdown", map[string]interface{}{ "error": err.Error(), }) } // Close MongoDB connection if err := mongoClient.Disconnect(context.Background()); err != nil { log.Error("Failed to disconnect from MongoDB", map[string]interface{}{ "error": err.Error(), }) } log.Info("Server stopped gracefully", nil) // Cleanup logger to flush any remaining logs logger.Cleanup() } // setupRoutes configures all API routes func setupRoutes( e *echo.Echo, authHandler *handler.AuthHandler, customerAuthHandler *handler.CustomerAuthHandler, userAuthHandler *handler.UserAuthHandler, ) { // Legacy/Generic authentication routes (backward compatibility) auth := e.Group("/auth") auth.POST("/register", authHandler.Register) auth.POST("/login", authHandler.Login) auth.POST("/refresh", authHandler.RefreshToken) auth.POST("/change-password", authHandler.ChangePassword) auth.GET("/profile", authHandler.GetProfile) auth.POST("/logout", authHandler.Logout) // API version group api := e.Group("/api/v1") // Mobile app routes (customers) mobile := api.Group("/mobile") mobileAuth := mobile.Group("/auth") mobileAuth.POST("/register", customerAuthHandler.Register) mobileAuth.POST("/login", customerAuthHandler.Login) mobileAuth.POST("/refresh", customerAuthHandler.RefreshToken) mobileAuth.POST("/verify-email", customerAuthHandler.VerifyEmail) mobileAuth.POST("/resend-verification", customerAuthHandler.ResendVerification) // Protected mobile routes mobileAuth.POST("/change-password", customerAuthHandler.ChangePassword, customMiddleware.CustomerOnlyMiddleware()) mobileAuth.GET("/profile", customerAuthHandler.GetProfile, customMiddleware.CustomerOnlyMiddleware()) mobileAuth.POST("/logout", customerAuthHandler.Logout, customMiddleware.CustomerOnlyMiddleware()) // Mobile app tenders routes mobileTenders := mobile.Group("/tenders", customMiddleware.CustomerOnlyMiddleware()) // TODO: Add tender routes for customers _ = mobileTenders // Mobile app companies routes mobileCompanies := mobile.Group("/companies", customMiddleware.CustomerOnlyMiddleware()) // TODO: Add company routes for customers _ = mobileCompanies // Admin panel routes (panel users) admin := api.Group("/admin") adminAuth := admin.Group("/auth") adminAuth.POST("/login", userAuthHandler.Login) adminAuth.POST("/refresh", userAuthHandler.RefreshToken) // Protected admin auth routes adminAuth.POST("/change-password", userAuthHandler.ChangePassword, customMiddleware.UserOnlyMiddleware()) adminAuth.GET("/profile", userAuthHandler.GetProfile, customMiddleware.UserOnlyMiddleware()) adminAuth.POST("/logout", userAuthHandler.Logout, customMiddleware.UserOnlyMiddleware()) // Admin user management routes (admin only) adminUsers := admin.Group("/users", customMiddleware.UserRoleMiddleware(domain.UserRoleAdmin)) adminUsers.POST("", userAuthHandler.CreatePanelUser) adminUsers.PUT("/:user_id/permissions", userAuthHandler.UpdateUserPermissions) // Admin customer management routes adminCustomers := admin.Group("/customers", customMiddleware.UserRoleMiddleware(domain.UserRoleAdmin, domain.UserRoleModerator)) // TODO: Add customer management routes for admins _ = adminCustomers // Admin tender management routes adminTenders := admin.Group("/tenders", customMiddleware.UserPermissionMiddleware("manage_tenders")) // TODO: Add tender management routes for admins _ = adminTenders // Admin company management routes adminCompanies := admin.Group("/companies", customMiddleware.UserPermissionMiddleware("manage_companies")) // TODO: Add company management routes for admins _ = adminCompanies // Admin reports routes adminReports := admin.Group("/reports", customMiddleware.UserPermissionMiddleware("view_reports")) // TODO: Add reporting routes for admins _ = adminReports }