Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-03-15 22:50:04 +03:00
parent f08bbd5e28
commit 9c76a521c5
3 changed files with 121 additions and 386 deletions

View File

@@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
@@ -21,139 +22,38 @@ type ProductImageItem struct {
FileSize int64 `json:"file_size"` FileSize int64 `json:"file_size"`
Storage string `json:"storage_path"` Storage string `json:"storage_path"`
ContentURL string `json:"content_url"` ContentURL string `json:"content_url"`
UUID string `json:"uuid,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`
FullURL string `json:"full_url,omitempty"`
} }
func tokenizeImageFileName(fileName string) []string { var uuidPattern = regexp.MustCompile(`(?i)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`)
up := strings.ToUpper(strings.TrimSpace(fileName))
if up == "" { func normalizeDimParam(v string) string {
return nil s := strings.TrimSpace(v)
if s == "" || s == "0" {
return ""
} }
return strings.FieldsFunc(up, func(r rune) bool { return s
isUpper := r >= 'A' && r <= 'Z'
isDigit := r >= '0' && r <= '9'
if isUpper || isDigit || r == '_' {
return false
}
return true
})
} }
func isAllDigits(s string) bool { func extractImageUUID(storagePath, fileName string) string {
if s == "" { if m := uuidPattern.FindString(storagePath); m != "" {
return false return strings.ToLower(m)
} }
for _, r := range s { if m := uuidPattern.FindString(fileName); m != "" {
if r < '0' || r > '9' { return strings.ToLower(m)
return false
}
}
return true
}
func imageFileHasDim3Pattern(fileName string) bool {
tokens := tokenizeImageFileName(fileName)
for _, t := range tokens {
parts := strings.SplitN(t, "_", 2)
if len(parts) != 2 {
continue
}
if len(parts[0]) == 3 && isAllDigits(parts[0]) && isAllDigits(parts[1]) {
return true
}
}
return false
}
func firstThreeDigitToken(tokens []string) string {
for _, t := range tokens {
base := t
if idx := strings.Index(base, "_"); idx >= 0 {
base = base[:idx]
}
if len(base) == 3 && isAllDigits(base) {
return base
}
} }
return "" return ""
} }
func filePrimaryMatchesDim1(fileName, dim1 string) bool {
dim1 = strings.ToUpper(strings.TrimSpace(dim1))
if dim1 == "" {
return true
}
tokens := tokenizeImageFileName(fileName)
if len(tokens) == 0 {
return true
}
primary := firstThreeDigitToken(tokens)
if primary == "" {
return true
}
return primary == dim1
}
func imageFileMatches(fileName, dim1, dim3 string) bool {
dim1 = strings.ToUpper(strings.TrimSpace(dim1))
dim3 = strings.ToUpper(strings.TrimSpace(dim3))
if dim1 == "" && dim3 == "" {
return true
}
tokens := tokenizeImageFileName(fileName)
if len(tokens) == 0 {
return false
}
matchesToken := func(token, target string) bool {
if token == target {
return true
}
// "002" filtresi, dosya adindaki "002_1" gibi varyantlari da yakalamali.
if len(target) == 3 && isAllDigits(target) && strings.HasPrefix(token, target+"_") {
return true
}
return false
}
hasToken := func(target string) bool {
if target == "" {
return true
}
for _, t := range tokens {
if matchesToken(t, target) {
return true
}
}
return false
}
// dim1 filtresi varsa, dosya adindaki primary renk token'i farkliysa eslesme sayma.
// Ornek: "017--002_1" dosyasi dim1=002 icin degil, primary=017 oldugu icin dislanmali.
if dim1 != "" {
primary := firstThreeDigitToken(tokens)
if primary != "" && primary != dim1 {
return false
}
}
return hasToken(dim1) && hasToken(dim3)
}
//
// LIST PRODUCT IMAGES
//
// GET /api/product-images?code=...&dim1=...&dim3=... // GET /api/product-images?code=...&dim1=...&dim3=...
func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc { func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
reqID := strings.TrimSpace(r.Header.Get("X-Request-ID")) reqID := strings.TrimSpace(r.Header.Get("X-Request-ID"))
if reqID == "" { if reqID == "" {
reqID = uuid.NewString() reqID = uuid.NewString()
} }
w.Header().Set("X-Request-ID", reqID) w.Header().Set("X-Request-ID", reqID)
code := strings.TrimSpace(r.URL.Query().Get("code")) code := strings.TrimSpace(r.URL.Query().Get("code"))
@@ -161,10 +61,6 @@ func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
if dim1 == "" { if dim1 == "" {
dim1 = strings.TrimSpace(r.URL.Query().Get("color")) dim1 = strings.TrimSpace(r.URL.Query().Get("color"))
} }
dim1ID := strings.TrimSpace(r.URL.Query().Get("dim1_id"))
if dim1ID == "" {
dim1ID = strings.TrimSpace(r.URL.Query().Get("itemdim1"))
}
dim3 := strings.TrimSpace(r.URL.Query().Get("dim3")) dim3 := strings.TrimSpace(r.URL.Query().Get("dim3"))
if dim3 == "" { if dim3 == "" {
dim3 = strings.TrimSpace(r.URL.Query().Get("yaka")) dim3 = strings.TrimSpace(r.URL.Query().Get("yaka"))
@@ -172,62 +68,93 @@ func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
if dim3 == "" { if dim3 == "" {
dim3 = strings.TrimSpace(r.URL.Query().Get("renk2")) dim3 = strings.TrimSpace(r.URL.Query().Get("renk2"))
} }
dim1ID := strings.TrimSpace(r.URL.Query().Get("dim1_id"))
if dim1ID == "" {
dim1ID = strings.TrimSpace(r.URL.Query().Get("itemdim1"))
}
dim3ID := strings.TrimSpace(r.URL.Query().Get("dim3_id")) dim3ID := strings.TrimSpace(r.URL.Query().Get("dim3_id"))
if dim3ID == "" { if dim3ID == "" {
dim3ID = strings.TrimSpace(r.URL.Query().Get("itemdim3")) dim3ID = strings.TrimSpace(r.URL.Query().Get("itemdim3"))
} }
if code == "" { if code == "" {
slog.Warn("product_images.list.bad_request",
"req_id", reqID,
"path", r.URL.Path,
"query", r.URL.RawQuery,
"reason", "missing_code",
)
http.Error(w, "Eksik parametre: code gerekli", http.StatusBadRequest) http.Error(w, "Eksik parametre: code gerekli", http.StatusBadRequest)
return return
} }
// Rule: code -> mmitem.id
var mmItemID int64
err := pg.QueryRow(`
SELECT id
FROM mmitem
WHERE UPPER(REPLACE(COALESCE(code,''), ' ', '')) = UPPER(REPLACE(COALESCE($1,''), ' ', ''))
ORDER BY id
LIMIT 1
`, code).Scan(&mmItemID)
if err == sql.ErrNoRows {
err = pg.QueryRow(`
SELECT id
FROM mmitem
WHERE UPPER(REPLACE(REGEXP_REPLACE(COALESCE(code,''), '^.*-', ''), ' ', '')) =
UPPER(REPLACE(REGEXP_REPLACE(COALESCE($1,''), '^.*-', ''), ' ', ''))
ORDER BY id
LIMIT 1
`, code).Scan(&mmItemID)
}
if err != nil {
if err == sql.ErrNoRows {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode([]ProductImageItem{})
return
}
http.Error(w, "Gorsel sorgu hatasi: "+err.Error(), http.StatusInternalServerError)
return
}
// Rule:
// dim1!=0 && dim3!=0 => dimval1=dim1 AND dimval3=dim3
// dim1!=0 && dim3==0 => dimval1=dim1
// dim1==0 && dim3==0 => generic photos
dim1Filter := normalizeDimParam(dim1ID)
if dim1Filter == "" {
dim1Filter = normalizeDimParam(dim1)
}
dim3Filter := normalizeDimParam(dim3ID)
if dim3Filter == "" {
dim3Filter = normalizeDimParam(dim3)
}
query := ` query := `
SELECT SELECT
b.id, id,
b.file_name, COALESCE(file_name,'') AS file_name,
COALESCE(b.file_size,0) AS file_size, COALESCE(file_size,0) AS file_size,
COALESCE(b.storage_path,'') AS storage_path, COALESCE(storage_path,'') AS storage_path
UPPER(COALESCE(b.dimval1::text,'')) AS dimval1, FROM dfblob
UPPER(COALESCE(b.dimval2::text,'')) AS dimval2, WHERE typ='img'
UPPER(COALESCE(b.dimval3::text,'')) AS dimval3 AND src_table='mmitem'
FROM dfblob b AND src_id=$1`
JOIN mmitem i args := []interface{}{mmItemID}
ON i.id = b.src_id argPos := 2
WHERE b.typ = 'img' if dim1Filter != "" {
AND b.src_table = 'mmitem' query += fmt.Sprintf(" AND COALESCE(dimval1::text,'') = $%d", argPos)
AND ( args = append(args, dim1Filter)
-- exact code match (spaces ignored) argPos++
UPPER(REPLACE(COALESCE(i.code,''), ' ', '')) = UPPER(REPLACE(COALESCE($1,''), ' ', '')) if dim3Filter != "" {
-- core-code fallback only when there is no exact item code for the input query += fmt.Sprintf(" AND COALESCE(dimval3::text,'') = $%d", argPos)
OR ( args = append(args, dim3Filter)
NOT EXISTS ( argPos++
SELECT 1 }
FROM mmitem ix }
WHERE UPPER(REPLACE(COALESCE(ix.code,''), ' ', '')) = UPPER(REPLACE(COALESCE($1,''), ' ', '')) query += `
)
AND UPPER(REPLACE(REGEXP_REPLACE(COALESCE(i.code,''), '^.*-', ''), ' ', '')) =
UPPER(REPLACE(REGEXP_REPLACE(COALESCE($1,''), '^.*-', ''), ' ', ''))
)
)
ORDER BY ORDER BY
COALESCE(b.sort_order,999999), COALESCE(sort_order,999999),
b.zlins_dttm DESC, zlins_dttm DESC,
b.id DESC id DESC`
`
rows, err := pg.Query(query, code)
rows, err := pg.Query(query, args...)
if err != nil { if err != nil {
slog.Error("product_images.list.query_failed", slog.Error("product_images.list.query_failed",
"req_id", reqID, "req_id", reqID,
"code", code, "code", code,
@@ -237,142 +164,24 @@ ORDER BY
"dim3_id", dim3ID, "dim3_id", dim3ID,
"err", err.Error(), "err", err.Error(),
) )
http.Error(w, "Gorsel sorgu hatasi: "+err.Error(), http.StatusInternalServerError) http.Error(w, "Gorsel sorgu hatasi: "+err.Error(), http.StatusInternalServerError)
return return
} }
defer rows.Close() defer rows.Close()
items := make([]ProductImageItem, 0, 16) items := make([]ProductImageItem, 0, 16)
rowDim1ByID := make(map[int64]string, 16)
matchedByDim := make([]ProductImageItem, 0, 16)
matchedByName := make([]ProductImageItem, 0, 16)
matchedByNameDim1Only := make([]ProductImageItem, 0, 16)
dim1Upper := strings.ToUpper(strings.TrimSpace(dim1))
dim1IDUpper := strings.ToUpper(strings.TrimSpace(dim1ID))
dim3Upper := strings.ToUpper(strings.TrimSpace(dim3))
dim3IDUpper := strings.ToUpper(strings.TrimSpace(dim3ID))
for rows.Next() { for rows.Next() {
var it ProductImageItem var it ProductImageItem
var rowDim1, rowDim2, rowDim3 string if err := rows.Scan(&it.ID, &it.FileName, &it.FileSize, &it.Storage); err != nil {
if err := rows.Scan(
&it.ID,
&it.FileName,
&it.FileSize,
&it.Storage,
&rowDim1,
&rowDim2,
&rowDim3,
); err != nil {
continue continue
} }
it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID) it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID)
if u := extractImageUUID(it.Storage, it.FileName); u != "" {
it.UUID = u
it.ThumbURL = "/uploads/image/t300/" + u + ".jpg"
it.FullURL = "/uploads/image/" + u + ".jpg"
}
items = append(items, it) items = append(items, it)
rowDim1ByID[it.ID] = strings.TrimSpace(rowDim1)
dimMatched := true
if dim1IDUpper != "" {
dimMatched = dimMatched && (rowDim1 == dim1IDUpper)
} else if dim1Upper != "" {
// Bazı eski kayıtlarda dimval1 gerçek renk kodu yerine numeric id tutulmuş olabilir.
// Bu yüzden dimval karşılaştırması yardımcı; asıl fallback file_name token eşleşmesidir.
dimMatched = dimMatched && (rowDim1 == dim1Upper)
}
if dim3IDUpper != "" {
dimMatched = dimMatched && (rowDim3 == dim3IDUpper || rowDim2 == dim3IDUpper)
} else if dim3Upper != "" {
dimMatched = dimMatched && (rowDim3 == dim3Upper || rowDim2 == dim3Upper)
}
if dimMatched {
matchedByDim = append(matchedByDim, it)
}
if imageFileMatches(it.FileName, dim1Upper, dim3Upper) {
matchedByName = append(matchedByName, it)
}
if dim1Upper != "" && imageFileMatches(it.FileName, dim1Upper, "") {
matchedByNameDim1Only = append(matchedByNameDim1Only, it)
}
}
if dim1Upper != "" || dim1IDUpper != "" || dim3Upper != "" || dim3IDUpper != "" {
if dim3Upper != "" || dim3IDUpper != "" {
// dim3 verildiginde kesin varyant listesi oncelikli.
if dim3Upper != "" && len(matchedByName) > 0 {
items = matchedByName
} else if len(matchedByDim) > 0 {
items = matchedByDim
} else if dim3Upper != "" && len(matchedByNameDim1Only) > 0 {
// dim3 pattern'i olmayan legacy tek-renk isimlerde dim1-only fallback.
hasDim3Pattern := false
for _, it := range matchedByNameDim1Only {
if imageFileHasDim3Pattern(it.FileName) {
hasDim3Pattern = true
break
}
}
if !hasDim3Pattern {
targetDimval1 := make(map[string]struct{}, 4)
for _, it := range matchedByNameDim1Only {
if dv := rowDim1ByID[it.ID]; dv != "" {
targetDimval1[dv] = struct{}{}
}
}
clustered := make([]ProductImageItem, 0, len(items))
for _, it := range items {
if _, ok := targetDimval1[rowDim1ByID[it.ID]]; ok {
clustered = append(clustered, it)
}
}
items = clustered
} else {
items = []ProductImageItem{}
}
} else {
items = []ProductImageItem{}
}
} else {
if dim1IDUpper != "" && len(matchedByDim) > 0 {
items = matchedByDim
} else {
targetDimval1 := make(map[string]struct{}, 4)
for _, it := range matchedByName {
if dv := rowDim1ByID[it.ID]; dv != "" {
targetDimval1[dv] = struct{}{}
}
}
if len(targetDimval1) == 0 {
for _, it := range matchedByNameDim1Only {
if dv := rowDim1ByID[it.ID]; dv != "" {
targetDimval1[dv] = struct{}{}
}
}
}
if len(targetDimval1) > 0 {
clustered := make([]ProductImageItem, 0, len(items))
for _, it := range items {
if _, ok := targetDimval1[rowDim1ByID[it.ID]]; ok && filePrimaryMatchesDim1(it.FileName, dim1Upper) {
clustered = append(clustered, it)
}
}
items = clustered
} else if len(matchedByDim) > 0 {
items = matchedByDim
} else if len(matchedByName) > 0 {
items = matchedByName
} else if len(matchedByNameDim1Only) > 0 {
items = matchedByNameDim1Only
} else {
items = []ProductImageItem{}
}
}
}
} }
slog.Info("product_images.list.ok", slog.Info("product_images.list.ok",
@@ -390,35 +199,18 @@ ORDER BY
} }
} }
//
// GET IMAGE CONTENT
//
// GET /api/product-images/{id}/content // GET /api/product-images/{id}/content
func GetProductImageContentHandler(pg *sql.DB) http.HandlerFunc { func GetProductImageContentHandler(pg *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
reqID := strings.TrimSpace(r.Header.Get("X-Request-ID")) reqID := strings.TrimSpace(r.Header.Get("X-Request-ID"))
if reqID == "" { if reqID == "" {
reqID = uuid.NewString() reqID = uuid.NewString()
} }
w.Header().Set("X-Request-ID", reqID) w.Header().Set("X-Request-ID", reqID)
idStr := mux.Vars(r)["id"] idStr := mux.Vars(r)["id"]
id, err := strconv.ParseInt(idStr, 10, 64) id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil || id <= 0 { if err != nil || id <= 0 {
slog.Warn("product_images.content.bad_request",
"req_id", reqID,
"id_raw", idStr,
"path", r.URL.Path,
"reason", "invalid_id",
)
http.Error(w, "Gecersiz gorsel id", http.StatusBadRequest) http.Error(w, "Gecersiz gorsel id", http.StatusBadRequest)
return return
} }
@@ -432,89 +224,51 @@ func GetProductImageContentHandler(pg *sql.DB) http.HandlerFunc {
err = pg.QueryRow(` err = pg.QueryRow(`
SELECT SELECT
COALESCE(file_name,''), COALESCE(file_name,''),
COALESCE(storage_path,''), COALESCE(storage_path,''),
COALESCE(stored_in_db,false), COALESCE(stored_in_db,false),
bin bin
FROM dfblob FROM dfblob
WHERE id = $1 WHERE id = $1
AND typ = 'img' AND typ = 'img'
`, id).Scan(&fileName, &storagePath, &storedInDB, &binData) `, id).Scan(&fileName, &storagePath, &storedInDB, &binData)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
slog.Warn("product_images.content.not_found_row",
"req_id", reqID,
"id", id,
)
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
slog.Error("product_images.content.query_failed",
"req_id", reqID,
"id", id,
"err", err.Error(),
)
http.Error(w, "Gorsel okunamadi: "+err.Error(), http.StatusInternalServerError) http.Error(w, "Gorsel okunamadi: "+err.Error(), http.StatusInternalServerError)
return return
} }
// DB içinde binary saklıysa
if storedInDB && len(binData) > 0 { if storedInDB && len(binData) > 0 {
w.Header().Set("Content-Type", http.DetectContentType(binData)) w.Header().Set("Content-Type", http.DetectContentType(binData))
w.Header().Set("Cache-Control", "public, max-age=3600") w.Header().Set("Cache-Control", "public, max-age=3600")
_, _ = w.Write(binData) _, _ = w.Write(binData)
return return
} }
resolved, tried := resolveStoragePath(storagePath) resolved, _ := resolveStoragePath(storagePath)
if resolved == "" { if resolved == "" {
slog.Warn("product_images.content.file_not_found",
"req_id", reqID,
"id", id,
"file_name", fileName,
"storage_path", storagePath,
"tried", tried,
)
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
w.Header().Set("Cache-Control", "public, max-age=3600") w.Header().Set("Cache-Control", "public, max-age=3600")
http.ServeFile(w, r, resolved) http.ServeFile(w, r, resolved)
} }
} }
//
// FILE PATH RESOLVER
//
func resolveStoragePath(storagePath string) (string, []string) { func resolveStoragePath(storagePath string) (string, []string) {
raw := strings.TrimSpace(storagePath) raw := strings.TrimSpace(storagePath)
if raw == "" { if raw == "" {
return "", nil return "", nil
} }
if i := strings.Index(raw, "?"); i >= 0 { if i := strings.Index(raw, "?"); i >= 0 {
raw = raw[:i] raw = raw[:i]
} }
raw = strings.ReplaceAll(raw, "\\", "/") raw = strings.ReplaceAll(raw, "\\", "/")
if scheme := strings.Index(raw, "://"); scheme >= 0 { if scheme := strings.Index(raw, "://"); scheme >= 0 {
rest := raw[scheme+3:] rest := raw[scheme+3:]
if i := strings.Index(rest, "/"); i >= 0 { if i := strings.Index(rest, "/"); i >= 0 {
@@ -525,11 +279,9 @@ func resolveStoragePath(storagePath string) (string, []string) {
raw = strings.TrimPrefix(raw, "./") raw = strings.TrimPrefix(raw, "./")
raw = strings.TrimPrefix(raw, "/") raw = strings.TrimPrefix(raw, "/")
raw = strings.TrimPrefix(raw, "uploads/") raw = strings.TrimPrefix(raw, "uploads/")
raw = filepath.ToSlash(filepath.Clean(raw)) raw = filepath.ToSlash(filepath.Clean(raw))
relUploads := filepath.FromSlash(filepath.Join("uploads", raw)) relUploads := filepath.FromSlash(filepath.Join("uploads", raw))
candidates := []string{ candidates := []string{
filepath.Clean(storagePath), filepath.Clean(storagePath),
filepath.FromSlash(filepath.Clean(strings.TrimPrefix(storagePath, "/"))), filepath.FromSlash(filepath.Clean(strings.TrimPrefix(storagePath, "/"))),
@@ -541,7 +293,6 @@ func resolveStoragePath(storagePath string) (string, []string) {
} }
if root := strings.TrimSpace(os.Getenv("BLOB_ROOT")); root != "" { if root := strings.TrimSpace(os.Getenv("BLOB_ROOT")); root != "" {
candidates = append(candidates, candidates = append(candidates,
filepath.Join(root, raw), filepath.Join(root, raw),
filepath.Join(root, relUploads), filepath.Join(root, relUploads),
@@ -550,11 +301,9 @@ func resolveStoragePath(storagePath string) (string, []string) {
} }
for _, p := range candidates { for _, p := range candidates {
if p == "" { if p == "" {
continue continue
} }
if st, err := os.Stat(p); err == nil && !st.IsDir() { if st, err := os.Stat(p); err == nil && !st.IsDir() {
return p, candidates return p, candidates
} }

View File

@@ -486,14 +486,7 @@ function resolvePhotoDim3(item, secondColorDisplay = '') {
} }
function resolvePhotoDim1ID(item) { function resolvePhotoDim1ID(item) {
const candidates = [ const candidates = [item?.ItemDim1Code, item?.itemDim1Code, item?.ITEMDIM1CODE, item?.Beden]
item?.ItemDim1Code,
item?.itemDim1Code,
item?.ITEMDIM1CODE,
item?.Beden,
item?.RenkID,
item?.Renk_Id
]
for (const value of candidates) { for (const value of candidates) {
const s = String(value || '').trim() const s = String(value || '').trim()
if (/^\d+$/.test(s)) return s if (/^\d+$/.test(s)) return s
@@ -502,12 +495,7 @@ function resolvePhotoDim1ID(item) {
} }
function resolvePhotoDim3ID(item) { function resolvePhotoDim3ID(item) {
const candidates = [ const candidates = [item?.ItemDim3Code, item?.itemDim3Code, item?.ITEMDIM3CODE, item?.Renk2]
item?.ItemDim3Code,
item?.itemDim3Code,
item?.ITEMDIM3CODE,
item?.Renk2
]
for (const value of candidates) { for (const value of candidates) {
const s = String(value || '').trim() const s = String(value || '').trim()
if (/^\d+$/.test(s)) return s if (/^\d+$/.test(s)) return s
@@ -553,7 +541,7 @@ function normalizeUploadsPath(storagePath) {
function resolveProductImageUrl(item) { function resolveProductImageUrl(item) {
if (!item || typeof item !== 'object') { if (!item || typeof item !== 'object') {
return { contentUrl: '', publicUrl: '' } return { contentUrl: '', publicUrl: '', thumbUrl: '', fullUrl: '' }
} }
let contentUrl = '' let contentUrl = ''
@@ -577,11 +565,16 @@ function resolveProductImageUrl(item) {
} }
} }
return { contentUrl, publicUrl } const thumbUrl = String(item.thumb_url || item.thumbUrl || '').trim()
const fullUrl = String(item.full_url || item.fullUrl || '').trim()
return { contentUrl, publicUrl, thumbUrl, fullUrl }
} }
async function resolveProductImageUrlForCarousel(item) { async function resolveProductImageUrlForCarousel(item) {
const resolved = resolveProductImageUrl(item) const resolved = resolveProductImageUrl(item)
const fullUrl = String(resolved.fullUrl || '').trim()
if (fullUrl) return fullUrl
const contentUrl = String(resolved.contentUrl || '').trim() const contentUrl = String(resolved.contentUrl || '').trim()
if (contentUrl) { if (contentUrl) {
try { try {
@@ -597,7 +590,7 @@ async function resolveProductImageUrlForCarousel(item) {
} }
} }
const publicUrl = String(resolved.publicUrl || '').trim() const publicUrl = String(resolved.publicUrl || '').trim()
return String(publicUrl || contentUrl || '').trim() return String(publicUrl || fullUrl || contentUrl || '').trim()
} }
function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') { function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
@@ -670,7 +663,7 @@ async function ensureProductImage(code, color, secondColor = '', dim1Id = '', di
const resolved = resolveProductImageUrl(first) const resolved = resolveProductImageUrl(first)
productImageCache.value[key] = resolved.contentUrl || resolved.publicUrl || '' productImageCache.value[key] = resolved.thumbUrl || resolved.fullUrl || resolved.contentUrl || resolved.publicUrl || ''
productImageFallbackByKey.value[key] = resolved.contentUrl || '' productImageFallbackByKey.value[key] = resolved.contentUrl || ''
} catch (err) { } catch (err) {
console.warn('[ProductStockByAttributes] product image fetch failed', { code, color, err }) console.warn('[ProductStockByAttributes] product image fetch failed', { code, color, err })

View File

@@ -479,14 +479,7 @@ function resolvePhotoDim3(item, secondColorDisplay = '') {
} }
function resolvePhotoDim1ID(item) { function resolvePhotoDim1ID(item) {
const candidates = [ const candidates = [item?.ItemDim1Code, item?.itemDim1Code, item?.ITEMDIM1CODE, item?.Beden]
item?.ItemDim1Code,
item?.itemDim1Code,
item?.ITEMDIM1CODE,
item?.Beden,
item?.RenkID,
item?.Renk_Id
]
for (const value of candidates) { for (const value of candidates) {
const s = String(value || '').trim() const s = String(value || '').trim()
if (/^\d+$/.test(s)) return s if (/^\d+$/.test(s)) return s
@@ -495,12 +488,7 @@ function resolvePhotoDim1ID(item) {
} }
function resolvePhotoDim3ID(item) { function resolvePhotoDim3ID(item) {
const candidates = [ const candidates = [item?.ItemDim3Code, item?.itemDim3Code, item?.ITEMDIM3CODE, item?.Renk2]
item?.ItemDim3Code,
item?.itemDim3Code,
item?.ITEMDIM3CODE,
item?.Renk2
]
for (const value of candidates) { for (const value of candidates) {
const s = String(value || '').trim() const s = String(value || '').trim()
if (/^\d+$/.test(s)) return s if (/^\d+$/.test(s)) return s
@@ -542,7 +530,7 @@ function normalizeUploadsPath(storagePath) {
function resolveProductImageUrl(item) { function resolveProductImageUrl(item) {
if (!item || typeof item !== 'object') { if (!item || typeof item !== 'object') {
return { contentUrl: '', publicUrl: '' } return { contentUrl: '', publicUrl: '', thumbUrl: '', fullUrl: '' }
} }
let contentUrl = '' let contentUrl = ''
@@ -564,11 +552,16 @@ function resolveProductImageUrl(item) {
if (fileName) publicUrl = `/uploads/image/${fileName}` if (fileName) publicUrl = `/uploads/image/${fileName}`
} }
return { contentUrl, publicUrl } const thumbUrl = String(item.thumb_url || item.thumbUrl || '').trim()
const fullUrl = String(item.full_url || item.fullUrl || '').trim()
return { contentUrl, publicUrl, thumbUrl, fullUrl }
} }
async function resolveProductImageUrlForCarousel(item) { async function resolveProductImageUrlForCarousel(item) {
const resolved = resolveProductImageUrl(item) const resolved = resolveProductImageUrl(item)
const fullUrl = String(resolved.fullUrl || '').trim()
if (fullUrl) return fullUrl
const contentUrl = String(resolved.contentUrl || '').trim() const contentUrl = String(resolved.contentUrl || '').trim()
if (contentUrl) { if (contentUrl) {
try { try {
@@ -584,7 +577,7 @@ async function resolveProductImageUrlForCarousel(item) {
} }
} }
const publicUrl = String(resolved.publicUrl || '').trim() const publicUrl = String(resolved.publicUrl || '').trim()
return String(publicUrl || contentUrl || '').trim() return String(publicUrl || fullUrl || contentUrl || '').trim()
} }
function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') { function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
@@ -654,7 +647,7 @@ async function ensureProductImage(code, color, secondColor = '', dim1Id = '', di
const first = list[0] || null const first = list[0] || null
const resolved = resolveProductImageUrl(first) const resolved = resolveProductImageUrl(first)
productImageCache.value[key] = resolved.contentUrl || resolved.publicUrl || '' productImageCache.value[key] = resolved.thumbUrl || resolved.fullUrl || resolved.contentUrl || resolved.publicUrl || ''
productImageFallbackByKey.value[key] = resolved.contentUrl || '' productImageFallbackByKey.value[key] = resolved.contentUrl || ''
} catch (err) { } catch (err) {
console.warn('[ProductStockQuery] product image fetch failed', { code, color, err }) console.warn('[ProductStockQuery] product image fetch failed', { code, color, err })