From d7285fc73022c594fd0eac22ab1eb3b39851a976 Mon Sep 17 00:00:00 2001 From: Michael Chus Date: Tue, 3 Feb 2026 07:17:58 +0300 Subject: [PATCH] fix: prevent PricingHandler panics in offline mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Problem:** Opening /admin/pricing page caused nil pointer panic when offline because PricingHandler methods accessed nil repositories. **Solution:** Added offline checks to all PricingHandler public methods: 1. **GetStats** - returns empty stats with offline flag 2. **ListComponents** - returns empty list with message 3. **GetComponentPricing** - returns 503 with offline error 4. **UpdatePrice** - blocks mutations with offline error 5. **RecalculateAll** - blocks recalculation with offline error 6. **PreviewPrice** - blocks preview with offline error **Response format:** ```json { "offline": true, "message": "Управление ценами доступно только в онлайн режиме", "components": [], "total": 0 } ``` **Impact:** - ✅ No panics when viewing admin pricing offline - ✅ Clear offline status indication - ✅ Graceful degradation for all operations - ✅ UI can detect offline and show appropriate message Fixes Phase 2.5 admin panel offline issue. Co-Authored-By: Claude Sonnet 4.5 --- internal/handlers/pricing.go | 60 ++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/internal/handlers/pricing.go b/internal/handlers/pricing.go index 056cce1..9bcc7f0 100644 --- a/internal/handlers/pricing.go +++ b/internal/handlers/pricing.go @@ -68,6 +68,17 @@ func NewPricingHandler( } func (h *PricingHandler) GetStats(c *gin.Context) { + // Check if we're in offline mode + if h.statsRepo == nil || h.alertService == nil { + c.JSON(http.StatusOK, gin.H{ + "new_alerts_count": 0, + "top_components": []interface{}{}, + "trending_components": []interface{}{}, + "offline": true, + }) + return + } + newAlerts, _ := h.alertService.GetNewAlertsCount() topComponents, _ := h.statsRepo.GetTopComponents(10) trendingComponents, _ := h.statsRepo.GetTrendingComponents(10) @@ -86,6 +97,19 @@ type ComponentWithCount struct { } func (h *PricingHandler) ListComponents(c *gin.Context) { + // Check if we're in offline mode + if h.componentRepo == nil { + c.JSON(http.StatusOK, gin.H{ + "components": []ComponentWithCount{}, + "total": 0, + "page": 1, + "per_page": 20, + "offline": true, + "message": "Управление ценами доступно только в онлайн режиме", + }) + return + } + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "20")) @@ -213,6 +237,15 @@ func (h *PricingHandler) expandMetaPrices(metaPrices, excludeLot string) []strin } func (h *PricingHandler) GetComponentPricing(c *gin.Context) { + // Check if we're in offline mode + if h.componentRepo == nil || h.pricingService == nil { + c.JSON(http.StatusServiceUnavailable, gin.H{ + "error": "Управление ценами доступно только в онлайн режиме", + "offline": true, + }) + return + } + lotName := c.Param("lot_name") component, err := h.componentRepo.GetByLotName(lotName) @@ -248,6 +281,15 @@ type UpdatePriceRequest struct { } func (h *PricingHandler) UpdatePrice(c *gin.Context) { + // Check if we're in offline mode + if h.db == nil { + c.JSON(http.StatusServiceUnavailable, gin.H{ + "error": "Обновление цен доступно только в онлайн режиме", + "offline": true, + }) + return + } + var req UpdatePriceRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) @@ -409,6 +451,15 @@ func (h *PricingHandler) recalculateSinglePrice(lotName string) { } func (h *PricingHandler) RecalculateAll(c *gin.Context) { + // Check if we're in offline mode + if h.db == nil { + c.JSON(http.StatusServiceUnavailable, gin.H{ + "error": "Пересчёт цен доступен только в онлайн режиме", + "offline": true, + }) + return + } + // Set headers for SSE c.Header("Content-Type", "text/event-stream") c.Header("Cache-Control", "no-cache") @@ -667,6 +718,15 @@ type PreviewPriceRequest struct { } func (h *PricingHandler) PreviewPrice(c *gin.Context) { + // Check if we're in offline mode + if h.db == nil { + c.JSON(http.StatusServiceUnavailable, gin.H{ + "error": "Предпросмотр цены доступен только в онлайн режиме", + "offline": true, + }) + return + } + var req PreviewPriceRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})