← back to Letsbegin
fix_dwpx_images.js
115 lines
const https = require('https');
const http = require('http');
const fs = require('fs');
const { execFileSync } = require('child_process');
const STORE = 'designer-laboratory-sandbox.myshopify.com';
const TOKEN = process.env.SHOPIFY_PRODUCT_TOKEN;
if (!TOKEN) {
throw new Error('SHOPIFY_PRODUCT_TOKEN environment variable is required');
}
// DW Italian name → Astek English name mapping
const NAME_MAP = {
'spazzolato': 'brushed metal wallcovering',
'ondulato': 'corrugated wallcovering',
'coccodrillo': 'croc wallcovering',
'sbalzato': 'embossed metal wallcovering',
'metallico': 'metal wallcovering',
'tessuto metallico': 'metallic textile wallcovering',
'trapuntato': 'quilted vinyl wallcovering',
};
function gql(body) {
return new Promise((res, rej) => {
const data = JSON.stringify(body);
const req = https.request({ hostname: STORE, path: '/admin/api/2024-10/graphql.json', method: 'POST',
headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) }
}, r => { let c=''; r.on('data',d=>c+=d); r.on('end',()=>res(JSON.parse(c))); });
req.on('error', rej); req.write(data); req.end();
});
}
function download(url, dest) {
return new Promise((res, rej) => {
const mod = url.startsWith('https') ? https : http;
const file = fs.createWriteStream(dest);
mod.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, r => {
if (r.statusCode >= 300 && r.headers.location) { file.close(); return download(r.headers.location, dest).then(res).catch(rej); }
r.pipe(file); file.on('finish', () => { file.close(); res(); });
}).on('error', e => { file.close(); rej(e); });
});
}
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function main() {
console.log('=== DWPX Image Fixer (Astek Decadent) ===\n');
// Load Astek lookup
const astek = JSON.parse(fs.readFileSync('/tmp/astek_lookup.json'));
// Load no-image products
const products = JSON.parse(fs.readFileSync('/tmp/no_img_products.json'));
console.log(`${products.length} products need images\n`);
let ok = 0, noMatch = 0, errors = 0;
for (let i = 0; i < products.length; i++) {
const p = products[i];
// Parse: "Spazzolato - Bronze Wallcovering | Phillipe Romano"
const titleParts = p.title.split('|')[0].trim();
const dashParts = titleParts.split(' - ');
const dwPattern = (dashParts[0] || '').trim().toLowerCase();
const colorPart = (dashParts[1] || '').replace(/wallcovering/gi, '').trim().toLowerCase();
// Map DW name to Astek name
const astekPattern = NAME_MAP[dwPattern];
if (!astekPattern) { console.log(` [${i+1}] ${p.title.slice(0,40)} — no pattern map for "${dwPattern}"`); noMatch++; continue; }
const key = `${astekPattern}|${colorPart}`;
const match = astek[key];
if (!match || !match.img) { console.log(` [${i+1}] ${p.title.slice(0,40)} — no Astek match for "${key}"`); noMatch++; continue; }
// Download image
const tmpFile = '/tmp/dwpx_img.jpg';
try {
await download(match.img, tmpFile);
const fileSize = fs.statSync(tmpFile).size;
// Staged upload
const stage = await gql({
query: `mutation { stagedUploadsCreate(input: [{resource: IMAGE, filename: "${match.sku}.jpg", mimeType: "image/jpeg", httpMethod: POST, fileSize: "${fileSize}"}]) { stagedTargets { url parameters { name value } resourceUrl } } }`
});
const target = stage?.data?.stagedUploadsCreate?.stagedTargets?.[0];
if (!target) { errors++; continue; }
const curlArgs = ['-s', '-X', 'POST', target.url];
for (const param of target.parameters) curlArgs.push('-F', `${param.name}=${param.value}`);
curlArgs.push('-F', `file=@${tmpFile}`);
execFileSync('curl', curlArgs, { timeout: 30000 });
// Create media
const pid = p.id.split('/').pop();
const r = await gql({
query: `mutation { productCreateMedia(productId: "gid://shopify/Product/${pid}", media: [{originalSource: "${target.resourceUrl}", mediaContentType: IMAGE, alt: "${p.title.split('|')[0].trim()}"}]) { media { id } mediaUserErrors { message } } }`
});
if (r?.data?.productCreateMedia?.media?.[0]) {
ok++;
// Also set manufacturer_sku
await gql({
query: 'mutation metafieldsSet($m: [MetafieldsSetInput!]!) { metafieldsSet(metafields: $m) { metafields { key } userErrors { message } } }',
variables: { m: [{ ownerId: p.id, namespace: 'custom', key: 'manufacturer_sku', value: match.sku.split('__')[0], type: 'single_line_text_field' }] }
});
console.log(` [${i+1}] ${p.title.slice(0,40)} → ${match.sku} ✓`);
} else { errors++; }
} catch (e) { errors++; console.log(` [${i+1}] ERROR: ${e.message?.slice(0,50)}`); }
await sleep(800);
}
console.log(`\n=== DONE === Images: ${ok} | No match: ${noMatch} | Errors: ${errors}`);
}
main().catch(e => { console.error('Fatal:', e); process.exit(1); });