package routes import ( "database/sql" "encoding/json" "fmt" "net/http" "os" "path/filepath" "strconv" "strings" "github.com/gorilla/mux" ) type ProductImageItem struct { ID int64 `json:"id"` FileName string `json:"file_name"` FileSize int64 `json:"file_size"` Storage string `json:"storage_path"` ContentURL string `json:"content_url"` } // GET /api/product-images?code=...&color=... func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { code := strings.TrimSpace(r.URL.Query().Get("code")) color := strings.TrimSpace(r.URL.Query().Get("color")) if code == "" { http.Error(w, "Eksik parametre: code gerekli", http.StatusBadRequest) return } query := ` SELECT b.id, b.file_name, 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 i.code = $1 AND ($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 { http.Error(w, "Görsel sorgu hatası: "+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 { continue } it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID) items = append(items, it) } w.Header().Set("Content-Type", "application/json; charset=utf-8") _ = json.NewEncoder(w).Encode(items) } } // GET /api/product-images/{id}/content func GetProductImageContentHandler(pg *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { idStr := mux.Vars(r)["id"] id, err := strconv.ParseInt(idStr, 10, 64) if err != nil || id <= 0 { http.Error(w, "Geçersiz görsel id", http.StatusBadRequest) return } var ( fileName string storagePath string storedInDB bool binData []byte ) err = pg.QueryRow(` SELECT 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 { http.NotFound(w, r) return } http.Error(w, "Görsel okunamadı: "+err.Error(), http.StatusInternalServerError) return } if storedInDB && len(binData) > 0 { w.Header().Set("Content-Type", http.DetectContentType(binData)) w.Header().Set("Cache-Control", "public, max-age=3600") _, _ = w.Write(binData) return } resolved := resolveStoragePath(storagePath) if resolved == "" { http.NotFound(w, r) return } w.Header().Set("Cache-Control", "public, max-age=3600") http.ServeFile(w, r, resolved) } } func resolveStoragePath(storagePath string) string { raw := strings.TrimSpace(storagePath) if raw == "" { return "" } raw = strings.TrimPrefix(raw, "./") candidates := []string{ filepath.Clean(storagePath), filepath.Clean(raw), filepath.Join(".", raw), filepath.Join("..", raw), } if root := strings.TrimSpace(os.Getenv("BLOB_ROOT")); root != "" { candidates = append(candidates, filepath.Join(root, raw)) } for _, p := range candidates { if p == "" { continue } if st, err := os.Stat(p); err == nil && !st.IsDir() { return p } } return "" }