- Add Apollo Client for Saleor GraphQL API - Create GraphQL fragments (Product, Variant, Checkout) - Create GraphQL queries (Products, Checkout) - Create GraphQL mutations (Checkout operations) - Add TypeScript types for Saleor entities - Add product helper functions - Install @apollo/client and graphql dependencies Part of WordPress/WooCommerce → Saleor migration
450 lines
12 KiB
Markdown
450 lines
12 KiB
Markdown
# Media & Image Migration Guide
|
|
|
|
## Current Setup
|
|
|
|
### WordPress/WooCommerce (Current)
|
|
- **Storage:** MinIO
|
|
- **Bucket:** `manoon-media`
|
|
- **Plugin:** Advanced Media Offloader (ADVMO)
|
|
- **Endpoint:** `http://minio:9000`
|
|
- **Public URL:** `https://minio-api.nodecrew.me/manoon-media/`
|
|
|
|
### Saleor (New)
|
|
- **Storage:** MinIO (same instance)
|
|
- **Bucket:** `saleor`
|
|
- **Endpoint:** `http://minio.manoonoils:9000`
|
|
- **Media URL:** `/media/` (served via Saleor API)
|
|
- **PVC:** `saleor-media-pvc` (5GB local cache)
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────┐ ┌─────────────────┐
|
|
│ WordPress │ │ Saleor │
|
|
│ │ │ │
|
|
│ WooCommerce │ │ API/Dashboard│
|
|
│ │ │ │
|
|
└────────┬────────┘ └────────┬────────┘
|
|
│ │
|
|
│ ADVMO Plugin │ django-storages
|
|
│ (S3-compatible) │ (S3-compatible)
|
|
│ │
|
|
└───────────┬───────────────┘
|
|
│
|
|
┌───────────┴───────────┐
|
|
│ MinIO │
|
|
│ (S3-compatible │
|
|
│ object storage) │
|
|
└───────────┬───────────┘
|
|
│
|
|
┌───────────────┼───────────────┐
|
|
│ │ │
|
|
┌────▼────┐ ┌────▼────┐ ┌─────▼─────┐
|
|
│ manoon- │ │ saleor │ │ other │
|
|
│ media │ │ bucket │ │ buckets │
|
|
│ (WP) │ │(Saleor) │ │ │
|
|
└─────────┘ └─────────┘ └───────────┘
|
|
```
|
|
|
|
## Step 1: Verify Buckets
|
|
|
|
```bash
|
|
# Access MinIO container
|
|
kubectl exec -ti deployment/minio -n manoonoils -- /bin/sh
|
|
|
|
# List all buckets
|
|
mc alias set local http://localhost:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD
|
|
mc ls local
|
|
|
|
# Expected output:
|
|
# [bucket] manoon-media (WordPress)
|
|
# [bucket] saleor (Saleor)
|
|
# [bucket] other... (if any)
|
|
```
|
|
|
|
If `saleor` bucket doesn't exist, create it:
|
|
```bash
|
|
mc mb local/saleor
|
|
```
|
|
|
|
## Step 2: Image Migration Strategies
|
|
|
|
### Option A: Copy Images from WordPress to Saleor Bucket
|
|
|
|
**Best for:** Clean separation, full control
|
|
|
|
```bash
|
|
# Copy all images from WordPress bucket to Saleor bucket
|
|
kubectl exec -ti deployment/minio -n manoonoils -- \
|
|
mc cp --recursive local/manoon-media/wp-content/uploads/ local/saleor/
|
|
|
|
# Or sync (faster for subsequent runs)
|
|
kubectl exec -ti deployment/minio -n manoonoils -- \
|
|
mc mirror local/manoon-media/wp-content/uploads/ local/saleor/products/
|
|
```
|
|
|
|
**After copy, images will be at:**
|
|
- `http://minio-api.nodecrew.me/saleor/products/2024/01/image.jpg`
|
|
|
|
### Option B: Share Bucket (Keep WordPress Images in Place)
|
|
|
|
**Best for:** Quick migration, no duplication
|
|
|
|
Configure Saleor to read from `manoon-media` bucket:
|
|
|
|
```yaml
|
|
# Update deployment to use WordPress bucket temporarily
|
|
env:
|
|
- name: AWS_MEDIA_BUCKET_NAME
|
|
value: "manoon-media" # Instead of "saleor"
|
|
- name: MEDIA_URL
|
|
value: "https://minio-api.nodecrew.me/manoon-media/"
|
|
```
|
|
|
|
**Pros:** No copying needed
|
|
**Cons:** WordPress and Saleor share bucket (risk of conflicts)
|
|
|
|
### Option C: Keep Separate + URL Mapping
|
|
|
|
**Best for:** Gradual migration
|
|
|
|
1. Keep WordPress images in `manoon-media`
|
|
2. New Saleor uploads go to `saleor` bucket
|
|
3. Use URL mapping for old images
|
|
|
|
```typescript
|
|
// Storefront image component
|
|
const ProductImage = ({ imageUrl }) => {
|
|
// If image is from old WordPress, rewrite URL
|
|
const mappedUrl = imageUrl.includes('manoon-media')
|
|
? imageUrl.replace('manoon-media', 'saleor')
|
|
: imageUrl;
|
|
|
|
return <img src={mappedUrl} />;
|
|
};
|
|
```
|
|
|
|
## Step 3: Add Images to Saleor Products
|
|
|
|
### Saleor Product Media Structure
|
|
|
|
Saleor stores media in `product_productmedia` table:
|
|
|
|
```sql
|
|
-- Check table structure
|
|
\d product_productmedia
|
|
|
|
-- Columns:
|
|
-- id, product_id, image (file path), alt, sort_order, type
|
|
```
|
|
|
|
### Migration Script
|
|
|
|
```sql
|
|
-- Create temporary mapping table
|
|
CREATE TEMP TABLE wp_image_mapping (
|
|
wp_product_id INTEGER,
|
|
saleor_product_id INTEGER,
|
|
wp_image_url VARCHAR(500),
|
|
saleor_image_path VARCHAR(500)
|
|
);
|
|
|
|
-- After copying images to saleor bucket, insert media records
|
|
INSERT INTO product_productmedia (product_id, image, alt, sort_order, type)
|
|
SELECT
|
|
p.id as product_id,
|
|
'products/' || SPLIT_PART(m.saleor_image_path, '/', -1) as image,
|
|
p.name as alt,
|
|
0 as sort_order,
|
|
'IMAGE' as type
|
|
FROM temp_woocommerce_import t
|
|
JOIN product_product p ON p.slug = t.slug
|
|
JOIN wp_image_mapping m ON m.wp_product_id = t.wc_id;
|
|
```
|
|
|
|
### Using Saleor Dashboard (Manual)
|
|
|
|
For small catalogs, use the Saleor Dashboard:
|
|
1. Go to https://dashboard.manoonoils.com
|
|
2. Catalog → Products → Select product
|
|
3. Media tab → Upload images
|
|
4. Set alt text, sort order
|
|
|
|
### Using GraphQL API (Programmatic)
|
|
|
|
```graphql
|
|
mutation ProductMediaCreate($product: ID!, $image: Upload!, $alt: String) {
|
|
productMediaCreate(input: {product: $product, image: $image, alt: $alt}) {
|
|
media {
|
|
id
|
|
url
|
|
}
|
|
errors {
|
|
field
|
|
message
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Python script example:
|
|
```python
|
|
import requests
|
|
from saleor.graphql import Client
|
|
|
|
# Upload image to Saleor
|
|
def upload_product_image(product_id, image_path, alt_text):
|
|
url = "https://api.manoonoils.com/graphql/"
|
|
|
|
query = """
|
|
mutation ProductMediaCreate($product: ID!, $image: Upload!, $alt: String) {
|
|
productMediaCreate(input: {product: $product, image: $image, alt: $alt}) {
|
|
media { id url }
|
|
errors { field message }
|
|
}
|
|
}
|
|
"""
|
|
|
|
operations = {
|
|
"query": query,
|
|
"variables": {
|
|
"product": product_id,
|
|
"alt": alt_text
|
|
}
|
|
}
|
|
|
|
map_data = {"0": ["variables.image"]}
|
|
|
|
with open(image_path, 'rb') as f:
|
|
files = {
|
|
'operations': (None, json.dumps(operations)),
|
|
'map': (None, json.dumps(map_data)),
|
|
'0': (image_path, f, 'image/jpeg')
|
|
}
|
|
|
|
response = requests.post(url, files=files)
|
|
return response.json()
|
|
```
|
|
|
|
## Step 4: Handle Logos & Assets
|
|
|
|
### Option 1: Store in Saleor (Recommended)
|
|
|
|
Upload logos to Saleor as product media for a "Store" product, or serve via CDN:
|
|
|
|
```bash
|
|
# Upload logo to MinIO saleor bucket
|
|
mc cp logo.png local/saleor/assets/
|
|
mc cp favicon.ico local/saleor/assets/
|
|
```
|
|
|
|
**Access URLs:**
|
|
- Logo: `https://minio-api.nodecrew.me/saleor/assets/logo.png`
|
|
- Favicon: `https://minio-api.nodecrew.me/saleor/assets/favicon.ico`
|
|
|
|
### Option 2: Store in Next.js Public Folder
|
|
|
|
For storefront-specific assets:
|
|
|
|
```
|
|
storefront/
|
|
├── public/
|
|
│ ├── logo.png
|
|
│ ├── favicon.ico
|
|
│ └── images/
|
|
│ └── hero-banner.jpg
|
|
```
|
|
|
|
Access: `https://dev.manoonoils.com/logo.png`
|
|
|
|
### Option 3: Keep in WordPress (Transition Period)
|
|
|
|
Continue serving assets from WordPress during migration:
|
|
|
|
```typescript
|
|
// Storefront config
|
|
const ASSETS_URL = process.env.NEXT_PUBLIC_ASSETS_URL ||
|
|
'https://minio-api.nodecrew.me/manoon-media/assets/';
|
|
|
|
// Usage
|
|
<img src={`${ASSETS_URL}logo.png`} alt="Logo" />
|
|
```
|
|
|
|
## Step 5: Storefront Image Component
|
|
|
|
Handle both old and new image URLs:
|
|
|
|
```typescript
|
|
// components/ProductImage.tsx
|
|
import { useState } from 'react';
|
|
|
|
interface ProductImageProps {
|
|
url: string;
|
|
alt: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function ProductImage({ url, alt, className }: ProductImageProps) {
|
|
const [error, setError] = useState(false);
|
|
|
|
// Map old WordPress URLs to new Saleor URLs
|
|
const mappedUrl = url?.includes('manoon-media')
|
|
? url.replace('manoon-media', 'saleor')
|
|
: url;
|
|
|
|
if (error) {
|
|
return <div className="image-placeholder">No Image</div>;
|
|
}
|
|
|
|
return (
|
|
<img
|
|
src={mappedUrl}
|
|
alt={alt}
|
|
className={className}
|
|
onError={() => setError(true)}
|
|
loading="lazy"
|
|
/>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Step 6: Image Optimization
|
|
|
|
### Saleor Thumbnails
|
|
|
|
Saleor automatically generates thumbnails:
|
|
|
|
```graphql
|
|
query ProductImages {
|
|
product(slug: "organsko-maslinovo-ulje", channel: "default-channel") {
|
|
media {
|
|
id
|
|
url
|
|
alt
|
|
type
|
|
# Thumbnails
|
|
thumbnail(size: 255) {
|
|
url
|
|
}
|
|
thumbnail(size: 510) {
|
|
url
|
|
}
|
|
thumbnail(size: 1020) {
|
|
url
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Next.js Image Optimization
|
|
|
|
```typescript
|
|
import Image from 'next/image';
|
|
|
|
// Optimized image component
|
|
export function OptimizedProductImage({ media }) {
|
|
return (
|
|
<Image
|
|
src={media.thumbnail?.url || media.url}
|
|
alt={media.alt}
|
|
width={400}
|
|
height={400}
|
|
quality={80}
|
|
placeholder="blur"
|
|
blurDataURL={media.thumbnail?.url}
|
|
/>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Step 7: Bulk Image Migration Script
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# migrate-images.sh
|
|
|
|
# 1. Export WooCommerce product images list
|
|
kubectl exec deployment/wordpress -n manoonoils -- \
|
|
wp db query "SELECT p.ID, p.post_title, pm.meta_value as image_url
|
|
FROM wp_posts p
|
|
JOIN wp_postmeta pm ON p.ID = pm.post_id
|
|
WHERE p.post_type = 'product' AND pm.meta_key = '_wp_attached_file'" \
|
|
> /tmp/wp-images.csv
|
|
|
|
# 2. Copy images to Saleor bucket
|
|
while IFS=',' read -r product_id title image_path; do
|
|
echo "Copying: $image_path"
|
|
kubectl exec deployment/minio -n manoonoils -- \
|
|
mc cp "local/manoon-media/$image_path" "local/saleor/products/"
|
|
done < /tmp/wp-images.csv
|
|
|
|
# 3. Update Saleor database with image paths
|
|
# (Run SQL script to insert into product_productmedia)
|
|
```
|
|
|
|
## Step 8: Verification Checklist
|
|
|
|
- [ ] All products have at least one image
|
|
- [ ] Images load correctly in Saleor Dashboard
|
|
- [ ] Images display in storefront
|
|
- [ ] Thumbnails generate properly
|
|
- [ ] Alt text is set for SEO
|
|
- [ ] Logo loads correctly
|
|
- [ ] Favicon works
|
|
- [ ] No broken image links
|
|
|
|
## Troubleshooting
|
|
|
|
### Images not showing in Saleor Dashboard
|
|
|
|
```bash
|
|
# Check if Saleor can access MinIO
|
|
kubectl exec deployment/saleor-api -n saleor -- \
|
|
curl -I http://minio.manoonoils:9000/saleor/
|
|
|
|
# Check bucket permissions
|
|
kubectl exec deployment/minio -n manoonoils -- \
|
|
mc policy get local/saleor
|
|
|
|
# Set bucket to public (if needed)
|
|
kubectl exec deployment/minio -n manoonoils -- \
|
|
mc policy set public local/saleor
|
|
```
|
|
|
|
### Image URLs returning 404
|
|
|
|
1. Check image exists in bucket:
|
|
```bash
|
|
mc ls local/saleor/products/2024/01/
|
|
```
|
|
|
|
2. Check image path in database:
|
|
```sql
|
|
SELECT * FROM product_productmedia WHERE product_id = 1;
|
|
```
|
|
|
|
3. Verify MEDIA_URL configuration:
|
|
```bash
|
|
kubectl get deployment saleor-api -n saleor -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="MEDIA_URL")].value}'
|
|
```
|
|
|
|
## Summary
|
|
|
|
| Component | Current (WP) | Target (Saleor) | Action |
|
|
|-----------|--------------|-----------------|--------|
|
|
| **Product Images** | MinIO: `manoon-media` | MinIO: `saleor` | Copy or share bucket |
|
|
| **Logo** | WP media | MinIO: `saleor/assets/` or Next.js public | Upload to new location |
|
|
| **Favicon** | WP root | Next.js public or MinIO | Move to storefront |
|
|
| **Thumbnails** | WP generates | Saleor generates | Automatic |
|
|
| **CDN** | MinIO direct | MinIO direct or Cloudflare | Optional upgrade |
|
|
|
|
## Recommended Approach
|
|
|
|
1. **Create `saleor` bucket** in existing MinIO
|
|
2. **Copy** all product images from `manoon-media` to `saleor`
|
|
3. **Upload logos** to `saleor/assets/` or Next.js public folder
|
|
4. **Run SQL** to insert image records into `product_productmedia`
|
|
5. **Update storefront** to handle both old and new URLs during transition
|
|
6. **Test** all images load correctly
|