148 lines
4.4 KiB
Go
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
|
|
}
|