All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
387 lines
12 KiB
Markdown
387 lines
12 KiB
Markdown
# 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 `<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
|
|
```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`.
|