Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -23,25 +23,34 @@ type ProductImageItem struct {
|
||||
ContentURL string `json:"content_url"`
|
||||
}
|
||||
|
||||
//
|
||||
// LIST PRODUCT IMAGES
|
||||
//
|
||||
|
||||
// GET /api/product-images?code=...&color=...
|
||||
func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
reqID := strings.TrimSpace(r.Header.Get("X-Request-ID"))
|
||||
if reqID == "" {
|
||||
reqID = uuid.NewString()
|
||||
}
|
||||
|
||||
w.Header().Set("X-Request-ID", reqID)
|
||||
|
||||
code := strings.TrimSpace(r.URL.Query().Get("code"))
|
||||
color := strings.TrimSpace(r.URL.Query().Get("color"))
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
@@ -50,38 +59,59 @@ func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
|
||||
SELECT
|
||||
b.id,
|
||||
b.file_name,
|
||||
COALESCE(b.file_size, 0) AS file_size,
|
||||
COALESCE(b.storage_path, '') AS storage_path
|
||||
COALESCE(b.file_size,0) AS file_size,
|
||||
COALESCE(b.storage_path,'') AS storage_path
|
||||
FROM dfblob b
|
||||
JOIN mmitem i
|
||||
ON i.id = b.src_id
|
||||
WHERE b.typ = 'img'
|
||||
AND b.src_table = 'mmitem'
|
||||
AND UPPER(i.code) = UPPER($1)
|
||||
AND ($2 = '' OR b.file_name ILIKE '%' || '-' || $2 || '-%')
|
||||
ORDER BY COALESCE(b.sort_order, 999999), b.zlins_dttm DESC, b.id DESC
|
||||
AND (
|
||||
$2 = ''
|
||||
OR b.file_name ILIKE '%' || '-' || $2 || '-%'
|
||||
OR b.file_name ILIKE '%' || '-' || $2 || '_%'
|
||||
)
|
||||
ORDER BY
|
||||
COALESCE(b.sort_order,999999),
|
||||
b.zlins_dttm DESC,
|
||||
b.id DESC
|
||||
`
|
||||
|
||||
rows, err := pg.Query(query, code, color)
|
||||
|
||||
if err != nil {
|
||||
|
||||
slog.Error("product_images.list.query_failed",
|
||||
"req_id", reqID,
|
||||
"code", code,
|
||||
"color", color,
|
||||
"err", err.Error(),
|
||||
)
|
||||
|
||||
http.Error(w, "Gorsel sorgu hatasi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
items := make([]ProductImageItem, 0, 16)
|
||||
|
||||
for rows.Next() {
|
||||
|
||||
var it ProductImageItem
|
||||
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,
|
||||
); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID)
|
||||
|
||||
items = append(items, it)
|
||||
}
|
||||
|
||||
@@ -97,24 +127,35 @@ ORDER BY COALESCE(b.sort_order, 999999), b.zlins_dttm DESC, b.id DESC
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// GET IMAGE CONTENT
|
||||
//
|
||||
|
||||
// GET /api/product-images/{id}/content
|
||||
func GetProductImageContentHandler(pg *sql.DB) http.HandlerFunc {
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
reqID := strings.TrimSpace(r.Header.Get("X-Request-ID"))
|
||||
if reqID == "" {
|
||||
reqID = uuid.NewString()
|
||||
}
|
||||
|
||||
w.Header().Set("X-Request-ID", reqID)
|
||||
|
||||
idStr := mux.Vars(r)["id"]
|
||||
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
@@ -128,83 +169,89 @@ func GetProductImageContentHandler(pg *sql.DB) http.HandlerFunc {
|
||||
|
||||
err = pg.QueryRow(`
|
||||
SELECT
|
||||
COALESCE(file_name, ''),
|
||||
COALESCE(storage_path, ''),
|
||||
COALESCE(stored_in_db, false),
|
||||
COALESCE(file_name,''),
|
||||
COALESCE(storage_path,''),
|
||||
COALESCE(stored_in_db,false),
|
||||
bin
|
||||
FROM dfblob
|
||||
WHERE id = $1
|
||||
AND typ = 'img'
|
||||
`, id).Scan(&fileName, &storagePath, &storedInDB, &binData)
|
||||
|
||||
if err != nil {
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
|
||||
slog.Warn("product_images.content.not_found_row",
|
||||
"req_id", reqID,
|
||||
"id", id,
|
||||
)
|
||||
|
||||
http.NotFound(w, r)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
// DB içinde binary saklıysa
|
||||
if storedInDB && len(binData) > 0 {
|
||||
slog.Info("product_images.content.served_from_db",
|
||||
"req_id", reqID,
|
||||
"id", id,
|
||||
"file_name", fileName,
|
||||
"bytes", len(binData),
|
||||
)
|
||||
|
||||
w.Header().Set("Content-Type", http.DetectContentType(binData))
|
||||
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||
|
||||
_, _ = w.Write(binData)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
resolved, tried := resolveStoragePath(storagePath)
|
||||
|
||||
if resolved == "" {
|
||||
|
||||
slog.Warn("product_images.content.file_not_found",
|
||||
"req_id", reqID,
|
||||
"id", id,
|
||||
"stored_in_db", storedInDB,
|
||||
"file_name", fileName,
|
||||
"storage_path", storagePath,
|
||||
"tried", tried,
|
||||
)
|
||||
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
slog.Info("product_images.content.served_from_file",
|
||||
"req_id", reqID,
|
||||
"id", id,
|
||||
"file_name", fileName,
|
||||
"storage_path", storagePath,
|
||||
"resolved_path", resolved,
|
||||
)
|
||||
|
||||
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||
|
||||
http.ServeFile(w, r, resolved)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// FILE PATH RESOLVER
|
||||
//
|
||||
|
||||
func resolveStoragePath(storagePath string) (string, []string) {
|
||||
|
||||
raw := strings.TrimSpace(storagePath)
|
||||
|
||||
if raw == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// URL/query temizligi ve platforma uygun normalize
|
||||
if i := strings.Index(raw, "?"); i >= 0 {
|
||||
raw = raw[:i]
|
||||
}
|
||||
|
||||
raw = strings.ReplaceAll(raw, "\\", "/")
|
||||
|
||||
if scheme := strings.Index(raw, "://"); scheme >= 0 {
|
||||
rest := raw[scheme+3:]
|
||||
if i := strings.Index(rest, "/"); i >= 0 {
|
||||
@@ -215,47 +262,36 @@ func resolveStoragePath(storagePath string) (string, []string) {
|
||||
raw = strings.TrimPrefix(raw, "./")
|
||||
raw = strings.TrimPrefix(raw, "/")
|
||||
raw = strings.TrimPrefix(raw, "uploads/")
|
||||
|
||||
raw = filepath.ToSlash(filepath.Clean(raw))
|
||||
|
||||
relUploads := filepath.FromSlash(filepath.Join("uploads", raw))
|
||||
rawT300 := raw
|
||||
relUploadsT300 := relUploads
|
||||
if strings.Contains(filepath.ToSlash(relUploads), "uploads/image/") &&
|
||||
!strings.Contains(filepath.ToSlash(relUploads), "uploads/image/t300/") {
|
||||
rawT300 = strings.Replace(filepath.ToSlash(raw), "image/", "image/t300/", 1)
|
||||
relUploadsT300 = filepath.FromSlash(
|
||||
strings.Replace(filepath.ToSlash(relUploads), "uploads/image/", "uploads/image/t300/", 1),
|
||||
)
|
||||
}
|
||||
|
||||
candidates := []string{
|
||||
filepath.Clean(storagePath),
|
||||
filepath.FromSlash(filepath.Clean(strings.TrimPrefix(storagePath, "/"))),
|
||||
filepath.FromSlash(filepath.Clean(raw)),
|
||||
relUploads,
|
||||
relUploadsT300,
|
||||
filepath.Join(".", relUploads),
|
||||
filepath.Join(".", relUploadsT300),
|
||||
filepath.Join("..", relUploads),
|
||||
filepath.Join("..", relUploadsT300),
|
||||
filepath.Join("..", "..", relUploads),
|
||||
filepath.Join("..", "..", relUploadsT300),
|
||||
}
|
||||
|
||||
if root := strings.TrimSpace(os.Getenv("BLOB_ROOT")); root != "" {
|
||||
|
||||
candidates = append(candidates,
|
||||
filepath.Join(root, raw),
|
||||
filepath.Join(root, filepath.FromSlash(rawT300)),
|
||||
filepath.Join(root, relUploads),
|
||||
filepath.Join(root, relUploadsT300),
|
||||
filepath.Join(root, "uploads", raw),
|
||||
filepath.Join(root, "uploads", filepath.FromSlash(rawT300)),
|
||||
)
|
||||
}
|
||||
|
||||
for _, p := range candidates {
|
||||
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if st, err := os.Stat(p); err == nil && !st.IsDir() {
|
||||
return p, candidates
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user