// Package gateway provides API Gateway implementation. package gateway import ( "fmt" "net/http" "net/http/httputil" "net/url" "git.dcentral.systems/toolz/goplt/internal/client" "git.dcentral.systems/toolz/goplt/pkg/config" "git.dcentral.systems/toolz/goplt/pkg/logger" "git.dcentral.systems/toolz/goplt/pkg/registry" "github.com/gin-gonic/gin" ) // Gateway handles routing requests to backend services. type Gateway struct { config config.ConfigProvider log logger.Logger clientFactory *client.ServiceClientFactory registry registry.ServiceRegistry routes []RouteConfig } // RouteConfig defines a route configuration. type RouteConfig struct { Path string Service string AuthRequired bool } // NewGateway creates a new API Gateway instance. func NewGateway( cfg config.ConfigProvider, log logger.Logger, clientFactory *client.ServiceClientFactory, reg registry.ServiceRegistry, ) (*Gateway, error) { // Load route configurations routes := loadRoutes(cfg) return &Gateway{ config: cfg, log: log, clientFactory: clientFactory, registry: reg, routes: routes, }, nil } // SetupRoutes configures routes on the Gin router. func (g *Gateway) SetupRoutes(router *gin.Engine) { // Setup route handlers for _, route := range g.routes { route := route // Capture for closure router.Any(route.Path, g.handleRoute(route)) } // Default handler for unmatched routes router.NoRoute(func(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{ "error": "Route not found", "path": c.Request.URL.Path, }) }) } // handleRoute returns a handler function for a route. func (g *Gateway) handleRoute(route RouteConfig) gin.HandlerFunc { return func(c *gin.Context) { // TODO: Add authentication middleware if auth_required is true // TODO: Add rate limiting middleware // TODO: Add CORS middleware // Discover service instances ctx := c.Request.Context() instances, err := g.registry.Discover(ctx, route.Service) if err != nil { g.log.Error("Failed to discover service", logger.String("service", route.Service), logger.Error(err), ) c.JSON(http.StatusServiceUnavailable, gin.H{ "error": "Service unavailable", }) return } if len(instances) == 0 { g.log.Warn("No instances found for service", logger.String("service", route.Service), ) c.JSON(http.StatusServiceUnavailable, gin.H{ "error": "Service unavailable", }) return } // Use first healthy instance (load balancing can be added later) instance := instances[0] targetURL := fmt.Sprintf("http://%s:%d", instance.Address, instance.Port) // Create reverse proxy target, err := url.Parse(targetURL) if err != nil { g.log.Error("Failed to parse target URL", logger.String("url", targetURL), logger.Error(err), ) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Internal server error", }) return } proxy := httputil.NewSingleHostReverseProxy(target) proxy.ServeHTTP(c.Writer, c.Request) } } // loadRoutes loads route configurations from config. func loadRoutes(cfg config.ConfigProvider) []RouteConfig { // For now, return empty routes - will be loaded from config in future // This is a placeholder implementation return []RouteConfig{} }