// Filename: internal/domain/upstream/handler.go package upstream import ( "gemini-balancer/internal/errors" "gemini-balancer/internal/models" "gemini-balancer/internal/response" "net/url" "strconv" "github.com/gin-gonic/gin" ) type Handler struct { service *Service } func NewHandler(service *Service) *Handler { return &Handler{service: service} } // ------ DTOs and Validation ------ type CreateUpstreamRequest struct { URL string `json:"url" binding:"required"` Weight int `json:"weight" binding:"omitempty,gte=1,lte=1000"` Status string `json:"status" binding:"omitempty,oneof=active inactive"` Description string `json:"description"` } type UpdateUpstreamRequest struct { URL *string `json:"url"` Weight *int `json:"weight" binding:"omitempty,gte=1,lte=1000"` Status *string `json:"status" binding:"omitempty,oneof=active inactive"` Description *string `json:"description"` } func isValidURL(rawURL string) bool { u, err := url.ParseRequestURI(rawURL) if err != nil { return false } return u.Scheme == "http" || u.Scheme == "https" } // --- Handler --- func (h *Handler) CreateUpstream(c *gin.Context) { var req CreateUpstreamRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, errors.NewAPIError(errors.ErrInvalidJSON, err.Error())) return } if !isValidURL(req.URL) { response.Error(c, errors.NewAPIError(errors.ErrValidation, "Invalid URL format")) return } upstream := models.UpstreamEndpoint{ URL: req.URL, Weight: req.Weight, Status: req.Status, Description: req.Description, } if err := h.service.Create(&upstream); err != nil { response.Error(c, errors.ParseDBError(err)) return } response.Created(c, upstream) } func (h *Handler) ListUpstreams(c *gin.Context) { upstreams, err := h.service.List() if err != nil { response.Error(c, errors.ParseDBError(err)) return } response.Success(c, upstreams) } func (h *Handler) GetUpstream(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { response.Error(c, errors.NewAPIError(errors.ErrBadRequest, "Invalid ID format")) return } upstream, err := h.service.GetByID(id) if err != nil { response.Error(c, errors.ParseDBError(err)) return } response.Success(c, upstream) } func (h *Handler) UpdateUpstream(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { response.Error(c, errors.NewAPIError(errors.ErrBadRequest, "Invalid ID format")) return } var req UpdateUpstreamRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, errors.NewAPIError(errors.ErrInvalidJSON, err.Error())) return } upstream, err := h.service.GetByID(id) if err != nil { response.Error(c, errors.ParseDBError(err)) return } if req.URL != nil { if !isValidURL(*req.URL) { response.Error(c, errors.NewAPIError(errors.ErrValidation, "Invalid URL format")) return } upstream.URL = *req.URL } if req.Weight != nil { upstream.Weight = *req.Weight } if req.Status != nil { upstream.Status = *req.Status } if req.Description != nil { upstream.Description = *req.Description } if err := h.service.Update(upstream); err != nil { response.Error(c, errors.ParseDBError(err)) return } response.Success(c, upstream) } func (h *Handler) DeleteUpstream(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { response.Error(c, errors.NewAPIError(errors.ErrBadRequest, "Invalid ID format")) return } rowsAffected, err := h.service.Delete(id) if err != nil { response.Error(c, errors.ParseDBError(err)) return } if rowsAffected == 0 { response.Error(c, errors.ErrResourceNotFound) return } response.NoContent(c) } // RegisterRoutes func (h *Handler) RegisterRoutes(rg *gin.RouterGroup) { upstreamRoutes := rg.Group("/upstreams") { upstreamRoutes.POST("/", h.CreateUpstream) upstreamRoutes.GET("/", h.ListUpstreams) upstreamRoutes.GET("/:id", h.GetUpstream) upstreamRoutes.PUT("/:id", h.UpdateUpstream) upstreamRoutes.DELETE("/:id", h.DeleteUpstream) } }