# 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 ```python 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 ```python 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 ```bash 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 ```bash 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 ```bash 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 ```bash # 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: ```bash 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 ```bash # 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 `` 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 `` 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 ```bash 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 ```bash kubectl exec -n my-apps -l app.kubernetes.io/component=backend -- ls -la /app/uploads ``` **Check 3**: Verify ingress routes ```bash 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 ```bash curl -I https://api-brand-master.dvirlabs.com/uploads/products/some-file.jpg ``` ### Issue: Images disappear after pod restart **Check**: PVC is properly mounted ```bash 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**: ```bash 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: ```bash # 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: ```yaml 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`.