brand-master/IMAGE_UPLOAD_FIX.md
dvirlabs 9176e32e6f
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Fix image not load correcttlly
2026-05-08 18:07:46 +03:00

12 KiB

Image Upload & Loading Fix - Brand Master

Problem Summary

Images were not loading correctly in the Brand Master application due to:

  1. URL Resolution Issue: Images stored with relative paths (/uploads/products/...) were resolving to the frontend domain instead of the backend
  2. Missing Category Upload Endpoint: No dedicated endpoint for category image uploads
  3. Ingress Routing: No route from frontend domain to backend for serving static uploads
  4. No Fallback Handling: Broken images displayed when URLs were invalid

Solutions Implemented

1. Backend Changes

a. Updated Settings Configuration

File: backend/app/config.py

  • Added backend_url setting to store the backend URL
  • This is set via BACKEND_URL environment variable in Kubernetes
backend_url: str = "http://localhost:8000"

b. Enhanced Upload Utility

File: backend/app/utils.py

  • Modified save_upload_file() to accept backend_url parameter
  • Now returns full URLs (e.g., https://api-brand-master.dvirlabs.com/uploads/products/...) instead of relative paths
  • Maintains backward compatibility with relative paths if backend_url not provided
def save_upload_file(upload_file, folder: str = "products", backend_url: str = None) -> str:
    # ... saves file ...
    relative_path = f"/uploads/{folder}/{unique_filename}"
    if backend_url:
        backend_url = backend_url.rstrip('/')
        return f"{backend_url}{relative_path}"
    return relative_path

c. Updated Product Upload Endpoints

File: backend/app/routers/products.py

  • Updated /api/products/upload-image to use backend_url from settings
  • Updated /api/products/upload-images to use backend_url from settings
  • Images now return full URLs immediately

d. Added Category Upload Endpoint

File: backend/app/routers/categories.py

  • Added new endpoint: POST /api/categories/upload-image
  • Validates image file types (jpeg, png, jpg, webp, gif)
  • Returns full URL with backend domain
  • Saves to uploads/categories/ folder

2. Frontend Changes

a. Updated Admin Panel Image Upload

File: frontend/src/pages/Admin.jsx

Product Images:

  • Removed URL building logic (backend returns full URLs)
  • Simplified handleImageUpload() function
  • Backend now handles URL generation

Category Images:

  • Changed from /products/upload-image to /categories/upload-image
  • Removed URL building logic
  • Directly uses backend-returned full URLs

b. Added Fallback Image Handling

File: frontend/src/components/ProductCard.jsx

  • Added error state tracking with useState
  • Handles image load failures with onError handler
  • Falls back to placeholder image if product image fails to load
  • Fallback: https://via.placeholder.com/400x400?text={product-name}

File: frontend/src/components/CategoryCard.jsx

  • Added error state tracking with useState
  • Handles image load failures with onError handler
  • Falls back to placeholder image if category image fails to load
  • Fallback: https://via.placeholder.com/300x300?text={category-name}

3. Kubernetes/Helm Changes

a. Updated Frontend Ingress

File: charts/brand-master-chart/templates/frontend-ingress.yaml

  • Added /uploads path routing to backend service
  • This provides backward compatibility for any existing relative URLs
  • Routes /uploads/* requests from frontend domain to backend service
  • Ensures images work even if old relative URLs exist in database

Path Priority:

  1. /uploads → Backend Service (for images)
  2. / → Frontend Service (for everything else)

b. Persistence Configuration

Already Configured in manifests/brand-master/values.yaml:

  • PersistentVolumeClaim enabled for backend
  • Storage: 15Gi on NFS storage class
  • Mount path: /app/uploads
  • Ensures images persist across pod restarts

Deployment Instructions

1. Backend Deployment

cd ~/OneDrive/Desktop/gitea/brand-master

# Build new backend image
docker build -t harbor.dvirlabs.com/my-apps/brand-master-backend:latest -f backend/Dockerfile backend/

# Push to Harbor
docker push harbor.dvirlabs.com/my-apps/brand-master-backend:latest

2. Frontend Deployment

cd ~/OneDrive/Desktop/gitea/brand-master

# Build new frontend image
docker build -t harbor.dvirlabs.com/my-apps/brand-master-frontend:latest -f frontend/Dockerfile frontend/

# Push to Harbor
docker push harbor.dvirlabs.com/my-apps/brand-master-frontend:latest

3. Update Helm Deployment

cd ~/OneDrive/Desktop/gitea/my-apps

# Update image tags in values.yaml if using specific tags
# Or use :latest for auto-pull

# Upgrade Helm release
helm upgrade brand-master charts/brand-master-chart \
  -f manifests/brand-master/values.yaml \
  -n my-apps

4. Verify Deployment

# Check pods are running
kubectl get pods -n my-apps -l app.kubernetes.io/name=brand-master

# Check backend logs
kubectl logs -n my-apps -l app.kubernetes.io/component=backend --tail=50

# Check frontend logs
kubectl logs -n my-apps -l app.kubernetes.io/component=frontend --tail=50

# Verify PVC is mounted
kubectl describe pod -n my-apps -l app.kubernetes.io/component=backend | grep uploads

Testing Instructions

1. Test Category Image Upload

  1. Login as admin: https://brand-master.dvirlabs.com/admin
  2. Go to "Categories" tab
  3. Create or edit a category
  4. Upload an image
  5. Expected: Image preview shows immediately
  6. Save category
  7. Expected: Image displays on home page category cards
  8. Refresh page
  9. Expected: Image still loads correctly

2. Test Product Image Upload

  1. Login as admin
  2. Go to "Products" tab
  3. Create or edit a product
  4. Upload one or more images
  5. Expected: Image previews show immediately in upload section
  6. Save product
  7. Expected: Product displays correctly in products list
  8. View product detail page
  9. Expected: All images load and gallery works
  10. Refresh page
  11. Expected: All images still load correctly

3. Test Image Persistence

  1. Upload some product/category images
  2. Restart backend pod:
    kubectl rollout restart deployment -n my-apps brand-master-backend
    
  3. Wait for pod to be ready
  4. Expected: All previously uploaded images still load correctly

4. Test Fallback Handling

  1. Manually break an image URL in the database (or edit in admin with invalid URL)
  2. View product/category with broken image
  3. Expected: Placeholder image displays instead of broken image icon
  4. No console errors about failed image loads

5. Verify URLs in Database

# Connect to database pod
kubectl exec -it -n my-apps brand-master-db-0 -- psql -U brand_master_user -d brand_master_db

# Check product image URLs
SELECT id, name, images FROM product LIMIT 5;

# Check category image URLs
SELECT id, name, image FROM category;

Expected URL Format:

  • New images: https://api-brand-master.dvirlabs.com/uploads/products/...
  • Old images (if any): /uploads/products/... (will still work via ingress routing)

6. Test Direct Image Access

  1. Upload an image and note the URL returned
  2. Copy the full URL
  3. Open in new browser tab
  4. Expected: Image loads directly
  5. Test both domains:
    • https://api-brand-master.dvirlabs.com/uploads/... (backend)
    • https://brand-master.dvirlabs.com/uploads/... (frontend → routed to backend)

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                     Ingress (Traefik)                       │
└─────────────────────────────────────────────────────────────┘
         │                                    │
         │ api-brand-master.dvirlabs.com     │ brand-master.dvirlabs.com
         │                                    │
         ▼                                    ▼
┌─────────────────┐                  ┌─────────────────┐
│  Backend Svc    │                  │  Frontend Svc   │
│  (Port 8000)    │                  │  (Port 80)      │
└─────────────────┘                  └─────────────────┘
         │                                    │
         │                                    ├── /uploads → Backend Svc
         │                                    └── /* → Frontend Pod
         ▼
┌─────────────────┐
│  Backend Pod    │
│  - FastAPI      │
│  - Static Files │
│    at /uploads  │
└─────────────────┘
         │
         ▼
┌─────────────────┐
│  PVC (15Gi)     │
│  /app/uploads   │
└─────────────────┘

Image Flow

Upload Flow:

  1. Admin uploads image via frontend form
  2. Frontend sends file to /api/products/upload-image or /api/categories/upload-image
  3. Backend saves file to uploads/{folder}/{uuid}{ext} on PVC
  4. Backend returns full URL: https://api-brand-master.dvirlabs.com/uploads/{folder}/{filename}
  5. Frontend stores full URL in database

Display Flow (New Images):

  1. Frontend renders <img src="https://api-brand-master.dvirlabs.com/uploads/..."/>
  2. Browser requests image from backend domain
  3. Ingress routes to Backend Service
  4. FastAPI serves file via StaticFiles mount
  5. Image displays

Display Flow (Old Relative URLs - Backup):

  1. Frontend renders <img src="/uploads/..."/>
  2. Browser requests from frontend domain
  3. Ingress routes /uploads/* to Backend Service
  4. FastAPI serves file
  5. Image displays

Troubleshooting

Issue: Images still not loading

Check 1: Verify backend URL is set correctly

kubectl get pod -n my-apps -l app.kubernetes.io/component=backend -o jsonpath='{.items[0].spec.containers[0].env[?(@.name=="BACKEND_URL")].value}'

Should return: https://api-brand-master.dvirlabs.com

Check 2: Verify StaticFiles is mounted

kubectl exec -n my-apps -l app.kubernetes.io/component=backend -- ls -la /app/uploads

Check 3: Verify ingress routes

kubectl get ingress -n my-apps brand-master-frontend -o yaml

Should show /uploads path routing to backend service.

Check 4: Test direct backend access

curl -I https://api-brand-master.dvirlabs.com/uploads/products/some-file.jpg

Issue: Images disappear after pod restart

Check: PVC is properly mounted

kubectl get pvc -n my-apps
kubectl describe pvc -n my-apps brand-master-uploads-pvc

Verify:

  • STATUS: Bound
  • STORAGECLASS: nfs-client
  • CAPACITY: 15Gi

Issue: Upload fails with 500 error

Check backend logs:

kubectl logs -n my-apps -l app.kubernetes.io/component=backend --tail=100

Common causes:

  • Permissions issue on /app/uploads
  • PVC not mounted
  • Disk space full

Summary of Files Changed

Backend (Code Repo)

  • backend/app/config.py - Added backend_url setting
  • backend/app/utils.py - Updated save_upload_file to return full URLs
  • backend/app/routers/products.py - Updated to use backend_url
  • backend/app/routers/categories.py - Added upload endpoint

Frontend (Code Repo)

  • frontend/src/pages/Admin.jsx - Removed URL building, use backend URLs
  • frontend/src/components/ProductCard.jsx - Added fallback handling
  • frontend/src/components/CategoryCard.jsx - Added fallback handling

Kubernetes (GitOps Repo)

  • charts/brand-master-chart/templates/frontend-ingress.yaml - Added /uploads routing

Next Steps

  1. Deploy backend changes
  2. Deploy frontend changes
  3. Update Helm deployment with new ingress config
  4. Test all image upload scenarios
  5. Verify images persist across restarts
  6. Monitor logs for any errors

Rollback Plan

If issues occur after deployment:

# Rollback Helm release
helm rollback brand-master -n my-apps

# Or rollback individual components
kubectl rollout undo deployment/brand-master-backend -n my-apps
kubectl rollout undo deployment/brand-master-frontend -n my-apps

Environment Variables Reference

Required in backend deployment:

env:
  BACKEND_URL: "https://api-brand-master.dvirlabs.com"  # Must be set!
  FRONTEND_URL: "https://brand-master.dvirlabs.com"
  PYTHONUNBUFFERED: "1"
  # ... other env vars ...

Already configured in manifests/brand-master/values.yaml.