Files
bssapp/svc/routes/product_pricing.go
2026-04-22 11:20:13 +03:00

148 lines
4.4 KiB
Go

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
}