package routes import ( "bssapp-backend/auth" "bssapp-backend/queries" "context" "encoding/json" "errors" "log" "net/http" "strconv" "strings" "time" ) // GET /api/pricing/products func GetProductPricingListHandler(w http.ResponseWriter, r *http.Request) { started := time.Now() traceID := buildPricingTraceID(r) w.Header().Set("X-Trace-ID", traceID) claims, ok := auth.GetClaimsFromContext(r.Context()) if !ok || claims == nil { log.Printf("[ProductPricing] trace=%s unauthorized method=%s path=%s", traceID, r.Method, r.URL.Path) http.Error(w, "unauthorized", http.StatusUnauthorized) return } log.Printf("[ProductPricing] trace=%s start user=%s id=%d", traceID, claims.Username, claims.ID) // Cloudflare upstream timeout is lower than 180s; fail fast and return API 504 instead of CDN 524. ctx, cancel := context.WithTimeout(r.Context(), 110*time.Second) defer cancel() limit := 500 if raw := strings.TrimSpace(r.URL.Query().Get("limit")); raw != "" { if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 && parsed <= 500 { limit = parsed } } page := 1 if raw := strings.TrimSpace(r.URL.Query().Get("page")); raw != "" { if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 { page = parsed } } filters := queries.ProductPricingFilters{ Search: strings.TrimSpace(r.URL.Query().Get("q")), ProductCode: splitCSVParam(r.URL.Query().Get("product_code")), BrandGroup: splitCSVParam(r.URL.Query().Get("brand_group_selection")), AskiliYan: splitCSVParam(r.URL.Query().Get("askili_yan")), Kategori: splitCSVParam(r.URL.Query().Get("kategori")), UrunIlkGrubu: splitCSVParam(r.URL.Query().Get("urun_ilk_grubu")), UrunAnaGrubu: splitCSVParam(r.URL.Query().Get("urun_ana_grubu")), UrunAltGrubu: splitCSVParam(r.URL.Query().Get("urun_alt_grubu")), Icerik: splitCSVParam(r.URL.Query().Get("icerik")), Karisim: splitCSVParam(r.URL.Query().Get("karisim")), Marka: splitCSVParam(r.URL.Query().Get("marka")), } pageResult, err := queries.GetProductPricingPage(ctx, page, limit, filters) if err != nil { if isPricingTimeoutLike(err, ctx.Err()) { log.Printf( "[ProductPricing] trace=%s timeout user=%s id=%d duration_ms=%d err=%v", traceID, claims.Username, claims.ID, time.Since(started).Milliseconds(), err, ) http.Error(w, "Urun fiyatlandirma listesi zaman asimina ugradi", http.StatusGatewayTimeout) return } log.Printf( "[ProductPricing] trace=%s query_error user=%s id=%d duration_ms=%d err=%v", traceID, claims.Username, claims.ID, time.Since(started).Milliseconds(), err, ) http.Error(w, "Urun fiyatlandirma listesi alinamadi: "+err.Error(), http.StatusInternalServerError) return } log.Printf( "[ProductPricing] trace=%s success user=%s id=%d page=%d limit=%d count=%d total=%d total_pages=%d duration_ms=%d", traceID, claims.Username, claims.ID, pageResult.Page, limit, len(pageResult.Rows), pageResult.TotalCount, pageResult.TotalPages, time.Since(started).Milliseconds(), ) w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("X-Total-Count", strconv.Itoa(pageResult.TotalCount)) w.Header().Set("X-Total-Pages", strconv.Itoa(pageResult.TotalPages)) w.Header().Set("X-Page", strconv.Itoa(pageResult.Page)) _ = json.NewEncoder(w).Encode(pageResult.Rows) } func buildPricingTraceID(r *http.Request) string { if r != nil { if id := strings.TrimSpace(r.Header.Get("X-Request-ID")); id != "" { return id } if id := strings.TrimSpace(r.Header.Get("X-Correlation-ID")); id != "" { return id } } return "pricing-" + strconv.FormatInt(time.Now().UnixNano(), 36) } func isPricingTimeoutLike(err error, ctxErr error) bool { if errors.Is(err, context.DeadlineExceeded) || errors.Is(ctxErr, context.DeadlineExceeded) { return true } if err == nil { return false } e := strings.ToLower(err.Error()) return strings.Contains(e, "timeout") || strings.Contains(e, "i/o timeout") || strings.Contains(e, "wsarecv") || strings.Contains(e, "connection attempt failed") || strings.Contains(e, "no connection could be made") || strings.Contains(e, "failed to respond") } func splitCSVParam(raw string) []string { raw = strings.TrimSpace(raw) if raw == "" { return nil } parts := strings.Split(raw, ",") out := make([]string, 0, len(parts)) for _, p := range parts { p = strings.TrimSpace(p) if p == "" { continue } out = append(out, p) } return out }