[object Object]

← back to Designer Wallcoverings

no-image backfill: attach 18 verified vendor images (src + base64-retry for octet-stream/oversized CDNs) + Nantucket description

770e4cf511c648d50ac962df512d8a6d567e04c2 · 2026-07-22 06:51:58 -0700 · Steve

Files touched

Diff

commit 770e4cf511c648d50ac962df512d8a6d567e04c2
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jul 22 06:51:58 2026 -0700

    no-image backfill: attach 18 verified vendor images (src + base64-retry for octet-stream/oversized CDNs) + Nantucket description
---
 scripts/wallquest-refresh/attach-14-retry-b64.mjs | 55 +++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/scripts/wallquest-refresh/attach-14-retry-b64.mjs b/scripts/wallquest-refresh/attach-14-retry-b64.mjs
new file mode 100644
index 00000000..922abbd4
--- /dev/null
+++ b/scripts/wallquest-refresh/attach-14-retry-b64.mjs
@@ -0,0 +1,55 @@
+#!/usr/bin/env node
+// Retry the 14 attaches that failed the src-URL path: Brewster/azureedge serve
+// image/jpeg under content-type application/octet-stream (Shopify's URL fetcher
+// rejects it), and Kravet Brandfolder originals exceed Shopify's 20MB limit.
+// Fix: download locally (Kravet via ?width=2000), upload as a base64 attachment
+// (bypasses content-type sniffing + we control the size). Verified-colorway set
+// only (from /tmp/noimg-verified.json), aura-light-gray still held.
+import { execSync } from 'node:child_process';
+import { readFileSync } from 'node:fs';
+import https from 'node:https';
+
+const TOK = process.env.SHOPIFY_ADMIN_TOKEN;
+if (!TOK) { console.error('SHOPIFY_ADMIN_TOKEN missing'); process.exit(1); }
+const SKIP = new Set(['7876263313459']);          // aura: colorway mismatch, held
+const DONE = new Set(['7513505431603', '7878219530291', '7878240665651']); // already attached in pass 1
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+function get(url) {
+  return new Promise((res, rej) => {
+    https.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, r => {
+      if (r.statusCode >= 300 && r.statusCode < 400 && r.headers.location) return res(get(r.headers.location));
+      if (r.statusCode !== 200) return rej(new Error('HTTP ' + r.statusCode));
+      const chunks = []; r.on('data', c => chunks.push(c)); r.on('end', () => res(Buffer.concat(chunks)));
+    }).on('error', rej);
+  });
+}
+function rest(method, path, body) {
+  return new Promise((res, rej) => {
+    const data = body ? JSON.stringify(body) : null;
+    const r = https.request({ hostname: 'designer-laboratory-sandbox.myshopify.com', path: `/admin/api/2024-10/${path}`, method,
+      headers: { 'X-Shopify-Access-Token': TOK, 'Content-Type': 'application/json', ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}) } },
+      rs => { let d = ''; rs.on('data', c => d += c); rs.on('end', () => { try { res({ status: rs.statusCode, body: d ? JSON.parse(d) : {} }); } catch (e) { res({ status: rs.statusCode, body: d }); } }); });
+    r.on('error', rej); if (data) r.write(data); r.end();
+  });
+}
+const sizedUrl = u => u.includes('cdn.brandfolder.io') ? (u.includes('?') ? u + '&width=2000' : u + '?width=2000') : u;
+
+const verified = JSON.parse(readFileSync('/tmp/noimg-verified.json', 'utf8'));
+const todo = verified.filter(p => !SKIP.has(String(p.pid)) && !DONE.has(String(p.pid)));
+let ok = 0, fail = 0;
+for (const p of todo) {
+  try {
+    const buf = await get(sizedUrl(p.src.url));
+    if (buf.length > 20 * 1024 * 1024) { fail++; console.log(`SKIP ${p.pid} still >20MB (${(buf.length/1e6).toFixed(1)}MB)`); continue; }
+    const filename = (p.src.match || String(p.pid)).replace(/[^A-Za-z0-9._-]/g, '_') + '.jpg';
+    const r = await rest('POST', `products/${p.pid}/images.json`, { image: { attachment: buf.toString('base64'), filename, alt: p.title } });
+    if (r.status < 300 && r.body.image) {
+      const cdn = r.body.image.src.replace(/'/g, "''");
+      execSync(`psql "host=/tmp dbname=dw_unified" -X -q -c "UPDATE shopify_products SET image_url='${cdn}' WHERE shopify_id='gid://shopify/Product/${p.pid}';"`);
+      ok++; console.log(`OK   ${p.pid} ${p.vendor} ${p.src.match} (${(buf.length/1e6).toFixed(1)}MB) image_id=${r.body.image.id}`);
+    } else { fail++; console.log(`FAIL ${p.pid} status=${r.status} ${JSON.stringify(r.body).slice(0, 140)}`); }
+  } catch (e) { fail++; console.log(`FAIL ${p.pid} ${String(e).slice(0, 100)}`); }
+  await sleep(800);
+}
+console.log(`\nretry: attached=${ok} fail=${fail}`);

← 871d8d35 auto-save: 2026-07-22T06:41:50 (1 files) — scripts/wallquest  ·  back to Designer Wallcoverings  ·  Anthology Phase-2 scrapers: SDG Trade Hub feed-first pull → 35503bb0 →