Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -6,35 +6,209 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetProductPricingList(ctx context.Context, limit int, afterProductCode string) ([]models.ProductPricing, error) {
|
||||
type ProductPricingFilters struct {
|
||||
Search string
|
||||
ProductCode []string
|
||||
BrandGroup []string
|
||||
AskiliYan []string
|
||||
Kategori []string
|
||||
UrunIlkGrubu []string
|
||||
UrunAnaGrubu []string
|
||||
UrunAltGrubu []string
|
||||
Icerik []string
|
||||
Karisim []string
|
||||
Marka []string
|
||||
}
|
||||
|
||||
type ProductPricingPage struct {
|
||||
Rows []models.ProductPricing
|
||||
TotalCount int
|
||||
TotalPages int
|
||||
Page int
|
||||
Limit int
|
||||
}
|
||||
|
||||
func GetProductPricingPage(ctx context.Context, page int, limit int, filters ProductPricingFilters) (ProductPricingPage, error) {
|
||||
result := ProductPricingPage{
|
||||
Rows: []models.ProductPricing{},
|
||||
TotalCount: 0,
|
||||
TotalPages: 0,
|
||||
Page: page,
|
||||
Limit: limit,
|
||||
}
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = 500
|
||||
}
|
||||
afterProductCode = strings.TrimSpace(afterProductCode)
|
||||
offset := (page - 1) * limit
|
||||
|
||||
paramIndex := 1
|
||||
args := make([]any, 0, 64)
|
||||
nextParam := func() string {
|
||||
name := "@p" + strconv.Itoa(paramIndex)
|
||||
paramIndex++
|
||||
return name
|
||||
}
|
||||
whereParts := []string{
|
||||
"ProductAtt42 IN ('SERI', 'AKSESUAR')",
|
||||
"IsBlocked = 0",
|
||||
"LEN(LTRIM(RTRIM(ProductCode))) = 13",
|
||||
}
|
||||
addInFilter := func(expr string, values []string) {
|
||||
clean := make([]string, 0, len(values))
|
||||
for _, v := range values {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
clean = append(clean, v)
|
||||
}
|
||||
if len(clean) == 0 {
|
||||
return
|
||||
}
|
||||
ors := make([]string, 0, len(clean))
|
||||
for _, v := range clean {
|
||||
p := nextParam()
|
||||
ors = append(ors, expr+" = "+p)
|
||||
args = append(args, v)
|
||||
}
|
||||
whereParts = append(whereParts, "("+strings.Join(ors, " OR ")+")")
|
||||
}
|
||||
brandGroupExpr := `CASE ABS(CHECKSUM(LTRIM(RTRIM(ProductCode)))) % 3
|
||||
WHEN 0 THEN 'MARKA GRUBU A'
|
||||
WHEN 1 THEN 'MARKA GRUBU B'
|
||||
ELSE 'MARKA GRUBU C'
|
||||
END`
|
||||
addInFilter("LTRIM(RTRIM(ProductCode))", filters.ProductCode)
|
||||
addInFilter(brandGroupExpr, filters.BrandGroup)
|
||||
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '')", filters.AskiliYan)
|
||||
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '')", filters.Kategori)
|
||||
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '')", filters.UrunIlkGrubu)
|
||||
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '')", filters.UrunAnaGrubu)
|
||||
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '')", filters.UrunAltGrubu)
|
||||
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '')", filters.Icerik)
|
||||
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '')", filters.Karisim)
|
||||
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '')", filters.Marka)
|
||||
if q := strings.TrimSpace(filters.Search); q != "" {
|
||||
p := nextParam()
|
||||
args = append(args, "%"+q+"%")
|
||||
whereParts = append(whereParts, "("+strings.Join([]string{
|
||||
"LTRIM(RTRIM(ProductCode)) LIKE " + p,
|
||||
"COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') LIKE " + p,
|
||||
"COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') LIKE " + p,
|
||||
"COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') LIKE " + p,
|
||||
"COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') LIKE " + p,
|
||||
"COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') LIKE " + p,
|
||||
"COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') LIKE " + p,
|
||||
"COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') LIKE " + p,
|
||||
"COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') LIKE " + p,
|
||||
}, " OR ")+")")
|
||||
}
|
||||
whereSQL := strings.Join(whereParts, " AND ")
|
||||
|
||||
countQuery := `
|
||||
SELECT COUNT(DISTINCT LTRIM(RTRIM(ProductCode)))
|
||||
FROM ProductFilterWithDescription('TR')
|
||||
WHERE ` + whereSQL + `;
|
||||
`
|
||||
var totalCount int
|
||||
if err := db.MssqlDB.QueryRowContext(ctx, countQuery, args...).Scan(&totalCount); err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.TotalCount = totalCount
|
||||
if totalCount == 0 {
|
||||
result.TotalPages = 0
|
||||
result.Page = 1
|
||||
return result, nil
|
||||
}
|
||||
totalPages := int(math.Ceil(float64(totalCount) / float64(limit)))
|
||||
if totalPages <= 0 {
|
||||
totalPages = 1
|
||||
}
|
||||
if page > totalPages {
|
||||
page = totalPages
|
||||
offset = (page - 1) * limit
|
||||
}
|
||||
result.Page = page
|
||||
result.Limit = limit
|
||||
result.TotalPages = totalPages
|
||||
|
||||
// Stage 1: fetch only paged products first (fast path).
|
||||
productQuery := `
|
||||
SELECT TOP (` + strconv.Itoa(limit) + `)
|
||||
LTRIM(RTRIM(ProductCode)) AS ProductCode,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') AS AskiliYan,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') AS Kategori,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') AS UrunIlkGrubu,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') AS UrunAnaGrubu,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') AS UrunAltGrubu,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') AS Icerik,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') AS Karisim,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') AS Marka
|
||||
FROM ProductFilterWithDescription('TR')
|
||||
WHERE ProductAtt42 IN ('SERI', 'AKSESUAR')
|
||||
AND IsBlocked = 0
|
||||
AND LEN(LTRIM(RTRIM(ProductCode))) = 13
|
||||
AND (@p1 = '' OR LTRIM(RTRIM(ProductCode)) > @p1)
|
||||
ORDER BY LTRIM(RTRIM(ProductCode));
|
||||
IF OBJECT_ID('tempdb..#req_codes') IS NOT NULL DROP TABLE #req_codes;
|
||||
IF OBJECT_ID('tempdb..#stock_base') IS NOT NULL DROP TABLE #stock_base;
|
||||
|
||||
SELECT
|
||||
f.ProductCode,
|
||||
MAX(f.BrandGroupSec) AS BrandGroupSec,
|
||||
MAX(f.AskiliYan) AS AskiliYan,
|
||||
MAX(f.Kategori) AS Kategori,
|
||||
MAX(f.UrunIlkGrubu) AS UrunIlkGrubu,
|
||||
MAX(f.UrunAnaGrubu) AS UrunAnaGrubu,
|
||||
MAX(f.UrunAltGrubu) AS UrunAltGrubu,
|
||||
MAX(f.Icerik) AS Icerik,
|
||||
MAX(f.Karisim) AS Karisim,
|
||||
MAX(f.Marka) AS Marka
|
||||
INTO #req_codes
|
||||
FROM (
|
||||
SELECT
|
||||
LTRIM(RTRIM(ProductCode)) AS ProductCode,
|
||||
` + brandGroupExpr + ` AS BrandGroupSec,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') AS AskiliYan,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') AS Kategori,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') AS UrunIlkGrubu,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') AS UrunAnaGrubu,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') AS UrunAltGrubu,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') AS Icerik,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') AS Karisim,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') AS Marka
|
||||
FROM ProductFilterWithDescription('TR')
|
||||
WHERE ` + whereSQL + `
|
||||
) f
|
||||
GROUP BY f.ProductCode;
|
||||
|
||||
CREATE CLUSTERED INDEX IX_req_codes_ProductCode ON #req_codes(ProductCode);
|
||||
|
||||
SELECT
|
||||
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
|
||||
SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
|
||||
INTO #stock_base
|
||||
FROM trStock s WITH(NOLOCK)
|
||||
INNER JOIN #req_codes rc
|
||||
ON rc.ProductCode = LTRIM(RTRIM(s.ItemCode))
|
||||
WHERE s.ItemTypeCode = 1
|
||||
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
|
||||
GROUP BY LTRIM(RTRIM(s.ItemCode));
|
||||
|
||||
CREATE CLUSTERED INDEX IX_stock_base_ItemCode ON #stock_base(ItemCode);
|
||||
|
||||
SELECT
|
||||
rc.ProductCode,
|
||||
rc.BrandGroupSec,
|
||||
rc.AskiliYan,
|
||||
rc.Kategori,
|
||||
rc.UrunIlkGrubu,
|
||||
rc.UrunAnaGrubu,
|
||||
rc.UrunAltGrubu,
|
||||
rc.Icerik,
|
||||
rc.Karisim,
|
||||
rc.Marka
|
||||
FROM #req_codes rc
|
||||
LEFT JOIN #stock_base sb
|
||||
ON sb.ItemCode = rc.ProductCode
|
||||
ORDER BY
|
||||
CAST(ROUND(ISNULL(sb.InventoryQty1, 0), 2) AS DECIMAL(18, 2)) DESC,
|
||||
rc.ProductCode ASC
|
||||
OFFSET ` + strconv.Itoa(offset) + ` ROWS
|
||||
FETCH NEXT ` + strconv.Itoa(limit) + ` ROWS ONLY;
|
||||
`
|
||||
|
||||
var (
|
||||
@@ -43,7 +217,7 @@ func GetProductPricingList(ctx context.Context, limit int, afterProductCode stri
|
||||
)
|
||||
for attempt := 1; attempt <= 3; attempt++ {
|
||||
var err error
|
||||
rows, err = db.MssqlDB.QueryContext(ctx, productQuery, afterProductCode)
|
||||
rows, err = db.MssqlDB.QueryContext(ctx, productQuery, args...)
|
||||
if err == nil {
|
||||
rowsErr = nil
|
||||
break
|
||||
@@ -60,7 +234,7 @@ func GetProductPricingList(ctx context.Context, limit int, afterProductCode stri
|
||||
}
|
||||
}
|
||||
if rowsErr != nil {
|
||||
return nil, rowsErr
|
||||
return result, rowsErr
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
@@ -69,6 +243,7 @@ func GetProductPricingList(ctx context.Context, limit int, afterProductCode stri
|
||||
var item models.ProductPricing
|
||||
if err := rows.Scan(
|
||||
&item.ProductCode,
|
||||
&item.BrandGroupSec,
|
||||
&item.AskiliYan,
|
||||
&item.Kategori,
|
||||
&item.UrunIlkGrubu,
|
||||
@@ -78,15 +253,16 @@ func GetProductPricingList(ctx context.Context, limit int, afterProductCode stri
|
||||
&item.Karisim,
|
||||
&item.Marka,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
return result, err
|
||||
}
|
||||
out = append(out, item)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
return result, err
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return out, nil
|
||||
result.Rows = out
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Stage 2: fetch metrics only for paged product codes.
|
||||
@@ -134,6 +310,7 @@ func GetProductPricingList(ctx context.Context, limit int, afterProductCode stri
|
||||
WHERE s.ItemTypeCode = 1
|
||||
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
|
||||
AND s.In_Qty1 > 0
|
||||
AND LTRIM(RTRIM(s.InnerProcessCode)) = 'OP'
|
||||
AND LTRIM(RTRIM(s.WarehouseCode)) IN (
|
||||
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||
@@ -214,7 +391,7 @@ func GetProductPricingList(ctx context.Context, limit int, afterProductCode stri
|
||||
|
||||
metricsRows, err := db.MssqlDB.QueryContext(ctx, metricsQuery, metricArgs...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("metrics query failed: %w", err)
|
||||
return result, fmt.Errorf("metrics query failed: %w", err)
|
||||
}
|
||||
defer metricsRows.Close()
|
||||
|
||||
@@ -237,12 +414,12 @@ func GetProductPricingList(ctx context.Context, limit int, afterProductCode stri
|
||||
&m.StockEntryDate,
|
||||
&m.LastPricingDate,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
return result, err
|
||||
}
|
||||
metricsByCode[strings.TrimSpace(code)] = m
|
||||
}
|
||||
if err := metricsRows.Err(); err != nil {
|
||||
return nil, err
|
||||
return result, err
|
||||
}
|
||||
|
||||
for i := range out {
|
||||
@@ -254,7 +431,8 @@ func GetProductPricingList(ctx context.Context, limit int, afterProductCode stri
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
result.Rows = out
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func isTransientMSSQLNetworkError(err error) bool {
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/lib/pq"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
@@ -497,25 +498,65 @@ func UserCreateRoute(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
// ROLES
|
||||
for _, role := range payload.Roles {
|
||||
_, _ = tx.Exec(queries.InsertUserRole, newID, role)
|
||||
role = strings.TrimSpace(role)
|
||||
if role == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := tx.Exec(queries.InsertUserRole, newID, role); err != nil {
|
||||
log.Printf("USER ROLE INSERT ERROR user_id=%d role=%q err=%v", newID, role, err)
|
||||
http.Error(w, "Rol eklenemedi", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DEPARTMENTS
|
||||
for _, d := range payload.Departments {
|
||||
_, _ = tx.Exec(queries.InsertUserDepartment, newID, d.Code)
|
||||
code := strings.TrimSpace(d.Code)
|
||||
if code == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := tx.Exec(queries.InsertUserDepartment, newID, code); err != nil {
|
||||
log.Printf("USER DEPARTMENT INSERT ERROR user_id=%d department=%q err=%v", newID, code, err)
|
||||
http.Error(w, "Departman eklenemedi", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// PIYASALAR
|
||||
for _, p := range payload.Piyasalar {
|
||||
_, _ = tx.Exec(queries.InsertUserPiyasa, newID, p.Code)
|
||||
code := strings.TrimSpace(p.Code)
|
||||
if code == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := tx.Exec(queries.InsertUserPiyasa, newID, code); err != nil {
|
||||
log.Printf("USER PIYASA INSERT ERROR user_id=%d piyasa=%q err=%v", newID, code, err)
|
||||
http.Error(w, "Piyasa eklenemedi", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// NEBIM
|
||||
for _, n := range payload.NebimUsers {
|
||||
_, _ = tx.Exec(queries.InsertUserNebim, newID, n.Username)
|
||||
username := strings.TrimSpace(n.Username)
|
||||
if username == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := tx.Exec(queries.InsertUserNebim, newID, username); err != nil {
|
||||
log.Printf("USER NEBIM INSERT ERROR user_id=%d username=%q err=%v", newID, username, err)
|
||||
http.Error(w, "Nebim kullanıcısı eklenemedi", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
if pe, ok := err.(*pq.Error); ok {
|
||||
log.Printf(
|
||||
"USER CREATE COMMIT ERROR user_id=%d code=%s detail=%s constraint=%s table=%s err=%v",
|
||||
newID, pe.Code, pe.Detail, pe.Constraint, pe.Table, err,
|
||||
)
|
||||
} else {
|
||||
log.Printf("USER CREATE COMMIT ERROR user_id=%d err=%v", newID, err)
|
||||
}
|
||||
http.Error(w, "Commit başarısız", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -32,13 +32,31 @@ func GetProductPricingListHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
limit := 500
|
||||
if raw := strings.TrimSpace(r.URL.Query().Get("limit")); raw != "" {
|
||||
if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 && parsed <= 10000 {
|
||||
if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 && parsed <= 500 {
|
||||
limit = parsed
|
||||
}
|
||||
}
|
||||
afterProductCode := strings.TrimSpace(r.URL.Query().Get("after_product_code"))
|
||||
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")),
|
||||
}
|
||||
|
||||
rows, err := queries.GetProductPricingList(ctx, limit+1, afterProductCode)
|
||||
pageResult, err := queries.GetProductPricingPage(ctx, page, limit, filters)
|
||||
if err != nil {
|
||||
if isPricingTimeoutLike(err, ctx.Err()) {
|
||||
log.Printf(
|
||||
@@ -63,38 +81,24 @@ func GetProductPricingListHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "Urun fiyatlandirma listesi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
hasMore := len(rows) > limit
|
||||
if hasMore {
|
||||
rows = rows[:limit]
|
||||
}
|
||||
nextCursor := ""
|
||||
if hasMore && len(rows) > 0 {
|
||||
nextCursor = strings.TrimSpace(rows[len(rows)-1].ProductCode)
|
||||
}
|
||||
|
||||
log.Printf(
|
||||
"[ProductPricing] trace=%s success user=%s id=%d limit=%d after=%q count=%d has_more=%t next=%q duration_ms=%d",
|
||||
"[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,
|
||||
afterProductCode,
|
||||
len(rows),
|
||||
hasMore,
|
||||
nextCursor,
|
||||
len(pageResult.Rows),
|
||||
pageResult.TotalCount,
|
||||
pageResult.TotalPages,
|
||||
time.Since(started).Milliseconds(),
|
||||
)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if hasMore {
|
||||
w.Header().Set("X-Has-More", "true")
|
||||
} else {
|
||||
w.Header().Set("X-Has-More", "false")
|
||||
}
|
||||
if nextCursor != "" {
|
||||
w.Header().Set("X-Next-Cursor", nextCursor)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(rows)
|
||||
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 {
|
||||
@@ -124,3 +128,20 @@ func isPricingTimeoutLike(err error, ctxErr error) bool {
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user