← back to Designer Wallcoverings
Daisy Bennett: room-setting images (62 generated on Kamatera, attached to live PDPs at last position, room_setting tag)
ee576edf235ca2d10ee46eccabb83c2b42bbf698 · 2026-07-06 14:32:22 -0700 · Steve
Files touched
A scripts/wallquest-refresh/attach-rooms-daisy-bennett.cjsA scripts/wallquest-refresh/room-batch-kamatera.cjsM scripts/wallquest-refresh/set-increment-daisy-bennett.cjs
Diff
commit ee576edf235ca2d10ee46eccabb83c2b42bbf698
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Jul 6 14:32:22 2026 -0700
Daisy Bennett: room-setting images (62 generated on Kamatera, attached to live PDPs at last position, room_setting tag)
---
.../attach-rooms-daisy-bennett.cjs | 27 ++++++++++++++++++++++
scripts/wallquest-refresh/room-batch-kamatera.cjs | 25 ++++++++++++++++++++
.../set-increment-daisy-bennett.cjs | 13 ++++++++---
3 files changed, 62 insertions(+), 3 deletions(-)
diff --git a/scripts/wallquest-refresh/attach-rooms-daisy-bennett.cjs b/scripts/wallquest-refresh/attach-rooms-daisy-bennett.cjs
new file mode 100644
index 00000000..d050a97c
--- /dev/null
+++ b/scripts/wallquest-refresh/attach-rooms-daisy-bennett.cjs
@@ -0,0 +1,27 @@
+// Attach generated room images to their live Shopify PDPs (Steve-authorized). Idempotent via
+// the 'room_setting' tag. Reads /tmp/rooms-manifest.txt (sku|pid|width) + /tmp/rooms-out/<sku>.jpg.
+const fs = require('fs'); const https = require('https');
+const TOK = process.env.SHOPIFY_ADMIN_TOKEN; const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
+const LIMIT = parseInt(process.env.LIMIT || '999', 10);
+function api(method, path, body){return new Promise((res,rej)=>{const data=body?JSON.stringify(body):null;const r=https.request({hostname:DOMAIN,path:`/admin/api/2024-10/${path}`,method,headers:{'X-Shopify-Access-Token':TOK,'Content-Type':'application/json',...(data?{'Content-Length':Buffer.byteLength(data)}:{})},timeout:45000},rs=>{let d='';rs.on('data',c=>d+=c);rs.on('end',()=>{try{const j=d?JSON.parse(d):{};rs.statusCode<300?res(j):rej(new Error(rs.statusCode+' '+d.slice(0,120)))}catch(e){rej(e)}})});r.on('error',rej);r.on('timeout',()=>{r.destroy();rej(new Error('timeout'))});if(data)r.write(data);r.end()})}
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+(async()=>{
+ const rows = fs.readFileSync('/tmp/rooms-manifest.txt','utf8').trim().split('\n').map(l=>l.split('|'));
+ let ok=0, skip=0, fail=0, i=0;
+ for (const [sku, pid] of rows) {
+ if (i++ >= LIMIT) break;
+ const img = `/tmp/rooms-out/${sku}.jpg`;
+ if (!fs.existsSync(img)) { skip++; continue; }
+ try {
+ const cur = await api('GET', `products/${pid}.json?fields=tags,images`);
+ if ((cur.product.tags||'').split(',').map(s=>s.trim()).includes('room_setting')) { skip++; continue; } // idempotent
+ const attachment = fs.readFileSync(img).toString('base64');
+ await api('POST', `products/${pid}/images.json`, { image: { attachment, alt: `${sku} room setting` } });
+ const tags = (cur.product.tags||'').split(',').map(s=>s.trim()).filter(Boolean); tags.push('room_setting');
+ await api('PUT', `products/${pid}.json`, { product: { id: Number(pid), tags: tags.join(', ') } });
+ ok++; if (ok<=3 || ok%15===0) console.log(` ✅ ${sku} room attached (${pid})`);
+ await sleep(300);
+ } catch(e){ console.error(` ❌ ${sku}: ${String(e).slice(0,120)}`); fail++; }
+ }
+ console.log(`\nATTACH DONE ok=${ok} skip=${skip} fail=${fail}`);
+})().catch(e=>{console.error('FATAL',e.message);process.exit(1);});
diff --git a/scripts/wallquest-refresh/room-batch-kamatera.cjs b/scripts/wallquest-refresh/room-batch-kamatera.cjs
new file mode 100644
index 00000000..6de2dd48
--- /dev/null
+++ b/scripts/wallquest-refresh/room-batch-kamatera.cjs
@@ -0,0 +1,25 @@
+// Runs ON Kamatera: loop /tmp/rooms-in/*.jpg → POST :3076/api/generate-room → /tmp/rooms-out/<sku>.png
+const fs = require('fs'); const http = require('http');
+const IN = '/tmp/rooms-in', OUT = '/tmp/rooms-out';
+fs.mkdirSync(OUT, { recursive: true });
+const ROOMS = ['living_room','bedroom','dining_room','office'];
+const ANGLES = ['angled_left','straight_on','angled_right'];
+const files = fs.readdirSync(IN).filter(f => f.endsWith('.jpg'));
+function gen(body){return new Promise((res,rej)=>{const data=JSON.stringify(body);const r=http.request({host:'127.0.0.1',port:3076,path:'/api/generate-room',method:'POST',headers:{'Content-Type':'application/json','Content-Length':Buffer.byteLength(data)},timeout:60000},rs=>{let d='';rs.on('data',c=>d+=c);rs.on('end',()=>{try{res(JSON.parse(d))}catch(e){rej(new Error('badjson '+d.slice(0,80)))}})});r.on('error',rej);r.on('timeout',()=>{r.destroy();rej(new Error('timeout'))});r.write(data);r.end()})}
+(async()=>{
+ let ok=0, fail=0, i=0;
+ for (const f of files) {
+ const sku = f.replace('.jpg','');
+ const out = `${OUT}/${sku}.png`;
+ if (fs.existsSync(out)) { ok++; continue; } // idempotent
+ try {
+ const b64 = fs.readFileSync(`${IN}/${f}`).toString('base64');
+ const j = await gen({ patternBase64:b64, roomType:ROOMS[i%ROOMS.length], angle:ANGLES[i%ANGLES.length], cameraDistance:8, patternWidth:36, patternHeight:36 });
+ if (j.success && j.image) { fs.writeFileSync(out, Buffer.from(j.image.replace(/^data:image\/\w+;base64,/,''),'base64')); ok++; }
+ else { fail++; console.log('FAIL',sku, JSON.stringify(j).slice(0,80)); }
+ } catch(e){ fail++; console.log('ERR',sku,String(e).slice(0,60)); }
+ i++;
+ if (i%10===0) console.log(`[${i}/${files.length}] ok=${ok} fail=${fail}`);
+ }
+ console.log(`BATCH DONE ok=${ok} fail=${fail} → ${OUT}`);
+})();
diff --git a/scripts/wallquest-refresh/set-increment-daisy-bennett.cjs b/scripts/wallquest-refresh/set-increment-daisy-bennett.cjs
index 7ff3b7e6..15d32913 100644
--- a/scripts/wallquest-refresh/set-increment-daisy-bennett.cjs
+++ b/scripts/wallquest-refresh/set-increment-daisy-bennett.cjs
@@ -30,9 +30,16 @@ async function apply(pid) {
if (!tags.includes('display_variant')) { tags.push('display_variant'); await shopify('PUT', `products/${pid}.json`, { product:{ id:Number(pid), tags: tags.join(', ') } }); }
}
(async () => {
- const daisy = execSync(`PGPASSWORD=DW2024SecurePass psql -h 127.0.0.1 -U dw_admin -d dw_unified -tAc "SELECT shopify_product_id FROM daisy_bennett_catalog WHERE on_shopify AND shopify_product_id IS NOT NULL ORDER BY dw_sku"`).toString().trim().split('\n').filter(Boolean);
- const ids = [...daisy, ...EXTRA_IDS].slice(0, LIMIT);
- console.log(`Setting ${INC}-yard increments on ${ids.length} products (${daisy.length} Daisy + ${EXTRA_IDS.length} extra)\n`);
+ // SOURCE_TABLE env lets this run against carl_robinson_catalog too; IDS env = explicit id list (skips DB).
+ let ids;
+ if (process.env.IDS) { ids = process.env.IDS.split(',').filter(Boolean); }
+ else {
+ const table = process.env.SOURCE_TABLE || 'daisy_bennett_catalog';
+ const rows = execSync(`PGPASSWORD=DW2024SecurePass psql -h 127.0.0.1 -U dw_admin -d dw_unified -tAc "SELECT shopify_product_id FROM ${table} WHERE on_shopify AND shopify_product_id IS NOT NULL ORDER BY dw_sku"`).toString().trim().split('\n').filter(Boolean);
+ ids = [...rows, ...EXTRA_IDS];
+ }
+ ids = ids.slice(0, LIMIT);
+ console.log(`Setting ${INC}-yard increments on ${ids.length} products (source=${process.env.IDS?'IDS':process.env.SOURCE_TABLE||'daisy_bennett_catalog'})\n`);
let ok=0, fail=0;
for (const pid of ids) {
try { await apply(pid); ok++; if (ok<=3 || ok%20===0) console.log(` ✅ ${pid} → min ${INC} / step ${INC} yd + display_variant`); }
← 080ee652 Kravet-under-PR re-vendor tool (dry-run default; --apply/--a
·
back to Designer Wallcoverings
·
wallquest write.cjs: also persist Roll Length (per-item unit cb2e90b9 →