[object Object]

← back to Designer Wallcoverings

wallquest write.cjs: also persist Roll Length (per-item unit basis: 8yd bolt vs 11yd roll), discontinued flag, price_updated_at. Full active refresh running via cracked login.

cb2e90b9bb7e0f9242ae1385abaa5723eeafda24 · 2026-07-06 14:33:26 -0700 · Steve

Files touched

Diff

commit cb2e90b9bb7e0f9242ae1385abaa5723eeafda24
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jul 6 14:33:26 2026 -0700

    wallquest write.cjs: also persist Roll Length (per-item unit basis: 8yd bolt vs 11yd roll), discontinued flag, price_updated_at. Full active refresh running via cracked login.
---
 scripts/wallquest-refresh/assist-login.cjs   |  6 +++--
 scripts/wallquest-refresh/verify-session.cjs | 34 ++++++++++++++++++++++++++++
 scripts/wallquest-refresh/write.cjs          |  9 ++++----
 3 files changed, 43 insertions(+), 6 deletions(-)

diff --git a/scripts/wallquest-refresh/assist-login.cjs b/scripts/wallquest-refresh/assist-login.cjs
index 921ce53f..bc18ad91 100644
--- a/scripts/wallquest-refresh/assist-login.cjs
+++ b/scripts/wallquest-refresh/assist-login.cjs
@@ -11,12 +11,14 @@ const PROFILE='/tmp/wq-chrome-profile'; fs.mkdirSync(PROFILE,{recursive:true});
 (async()=>{
   const ctx=await chromium.launchPersistentContext(PROFILE,{channel:'chrome',headless:false,viewport:{width:1280,height:900}});
   const page=ctx.pages()[0]||await ctx.newPage();
-  await page.goto('https://www.wallquest.com/login',{waitUntil:'domcontentloaded',timeout:45000});
+  for(let i=0;i<4;i++){try{await page.goto('https://www.wallquest.com/login',{waitUntil:'domcontentloaded',timeout:45000});break;}catch(e){console.error('goto retry '+i+': '+e.message.split('\n')[0]);await page.waitForTimeout(3000);}}
+  await page.bringToFront().catch(()=>{});
   await page.waitForTimeout(5000);
   try{ await page.fill('#Email','info@designerwallcoverings.com'); await page.fill('#Password',WPW); if(await page.$('#RememberMe')) await page.check('#RememberMe'); }catch(e){}
   console.error('>>> Chrome window open + pre-filled (incl. Remember-me → persistent session). SOLVE THE reCAPTCHA and click LOGIN now. Polling up to 180s...');
   let ok=false;
-  for(let t=0;t<45;t++){          // ~180s
+  const POLLS=parseInt(process.env.WQ_POLL||'75'); // ~300s (5 min)
+  for(let t=0;t<POLLS;t++){
     await page.waitForTimeout(4000);
     let st={};
     try{ st=await page.evaluate(()=>({url:location.href, leftLogin:!/\/login/i.test(location.href), loggedMarker:/log ?out|sign ?out/i.test(document.body.innerText) && !/please sign in/i.test(document.body.innerText)})); }catch(e){ continue; }
diff --git a/scripts/wallquest-refresh/verify-session.cjs b/scripts/wallquest-refresh/verify-session.cjs
new file mode 100644
index 00000000..2c04e5e8
--- /dev/null
+++ b/scripts/wallquest-refresh/verify-session.cjs
@@ -0,0 +1,34 @@
+// Verify saved session: homepage logged-in marker (definitive) + product price (with retry) + unit hunt.
+const {chromium}=require('playwright-core'); const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+const PROFILE='/tmp/wq-chrome-profile';
+const goto=async(p,u)=>{for(let i=0;i<3;i++){try{await p.goto(u,{waitUntil:'domcontentloaded',timeout:45000});return 1;}catch(e){await sleep(3000);}}return 0;};
+(async()=>{
+  const ctx=await chromium.launchPersistentContext(PROFILE,{channel:'chrome',headless:false,viewport:{width:1280,height:900}});
+  const page=ctx.pages()[0]||await ctx.newPage();
+  // 1) homepage — is the account logged in?
+  await goto(page,'https://www.wallquest.com/'); await sleep(8000);
+  const home=await page.evaluate(()=>{
+    const txt=document.body.innerText;
+    return {logout:/log ?out/i.test(txt), account:/my account/i.test(txt), signInPrompt:/welcome, please sign in|log ?in\b/i.test(txt), header:(document.querySelector('.header-links, .header')||{}).innerText?.replace(/\s+/g,' ').trim().slice(0,120)||''};
+  });
+  console.error('HOMEPAGE: logout='+home.logout+' account='+home.account+' signInPrompt='+home.signInPrompt);
+  console.error('HEADER: '+home.header);
+  // 2) product price with retry + unit hunt
+  await goto(page,'https://www.wallquest.com/1301900');
+  let price=null,area='',units=[];
+  for(let t=0;t<3 && !price;t++){
+    await sleep(10000);
+    const r=await page.evaluate(()=>{
+      const Q=s=>[...document.querySelectorAll(s)];
+      const pv=((Q('[class*=price-value]')[0]||{}).textContent||'').match(/[\d,]+\.\d{2}/);
+      const uh=[]; Q('*').forEach(e=>{const t=(e.textContent||'').replace(/\s+/g,' ').trim(); if(t.length<50 && /per bolt|per roll|per yard|yard bolt|sold per|8 ?yd|8 ?yard|\bbolt\b/i.test(t) && e.children.length<=2) uh.push(t);});
+      return {price:pv?pv[0]:null, area:((Q('.prices-stock-wrapper')[0]||{}).textContent||'').replace(/\s+/g,' ').trim().slice(0,160), units:[...new Set(uh)].slice(0,8)};
+    });
+    price=r.price; area=r.area; units=r.units;
+    console.error(`  product try ${t+1}: price=${price}`);
+  }
+  console.error('PRODUCT 1301900: price='+price);
+  console.error('PRICE AREA: '+area);
+  console.error('UNIT HITS: '+JSON.stringify(units));
+  await ctx.close().catch(()=>{});
+})();
diff --git a/scripts/wallquest-refresh/write.cjs b/scripts/wallquest-refresh/write.cjs
index a1129a98..511d628c 100644
--- a/scripts/wallquest-refresh/write.cjs
+++ b/scripts/wallquest-refresh/write.cjs
@@ -6,12 +6,13 @@ const PGPW=(ENVL.match(/^POSTGRES_PASSWORD=(.+)$/m)||[])[1];
 const ls=fs.readFileSync('/tmp/wq-refresh.jsonl','utf8').split('\n').filter(Boolean).map(l=>{try{return JSON.parse(l)}catch{return null}}).filter(Boolean);
 const esc=s=>String(s==null?'':s).replace(/'/g,"''").slice(0,200);
 const sql=v=>(v==null||v==='')?'NULL':"'"+esc(v)+"'";
-const rows=ls.filter(r=>r.priceValue||(r.specs&&Object.keys(r.specs).length)||(r.imgs&&r.imgs.length)).map(r=>{
+const rows=ls.filter(r=>r.priceValue||(r.specs&&Object.keys(r.specs).length)||(r.imgs&&r.imgs.length)||r.discontinued).map(r=>{
   const price=r.priceValue?parseFloat(String(r.priceValue).replace(/,/g,'')):null;
-  const w=(r.specs||{})['Roll Width']||null,m=(r.specs||{})['Material']||null,img=(r.imgs||[])[0]||null;
-  return `(${sql(r.sku)},${price==null?'NULL':price},${sql(w)},${sql(m)},${sql(img)})`;});
+  const w=(r.specs||{})['Roll Width']||null, len=(r.specs||{})['Roll Length']||null, m=(r.specs||{})['Material']||null, img=(r.imgs||[])[0]||null;
+  const disc=r.discontinued?'true':'false'; // Roll Length carries the per-item unit basis (e.g. "8 yd" bolt vs "11 yd" roll)
+  return `(${sql(r.sku)},${price==null?'NULL':price},${sql(w)},${sql(len)},${sql(m)},${sql(img)},${disc})`;});
 if(!rows.length){console.log('no rows');process.exit(0);}
-const stmt=`WITH d(sku,price,width,material,img) AS (VALUES ${rows.join(',')}) UPDATE wallquest_catalog c SET price_retail=COALESCE(NULLIF(d.price,0),c.price_retail),width=COALESCE(d.width,c.width),material=COALESCE(d.material,c.material),image_url=COALESCE(d.img,c.image_url),specs_scraped_at=now() FROM d WHERE c.mfr_sku=d.sku;`;
+const stmt=`WITH d(sku,price,width,length,material,img,discontinued) AS (VALUES ${rows.join(',')}) UPDATE wallquest_catalog c SET price_retail=COALESCE(NULLIF(d.price,0),c.price_retail),width=COALESCE(d.width,c.width),length=COALESCE(d.length,c.length),material=COALESCE(d.material,c.material),image_url=COALESCE(d.img,c.image_url),discontinued=d.discontinued::boolean,price_updated_at=CURRENT_DATE,specs_scraped_at=now() FROM d WHERE c.mfr_sku=d.sku;`;
 fs.writeFileSync('/tmp/wq-refresh.sql',stmt);
 const r=execSync(`PGPASSWORD='${PGPW}' ${PSQL} -h localhost -U dw_admin -d dw_unified -c "$(cat /tmp/wq-refresh.sql)"`,{encoding:'utf8',maxBuffer:1e8});
 console.log('write:',r.trim(),'| priced rows in feed:',ls.filter(x=>x.priceValue).length);

← ee576edf Daisy Bennett: room-setting images (62 generated on Kamatera  ·  back to Designer Wallcoverings  ·  Luxuria retitle dry-run tool — color:tag path proven lossy/c 98abb531 →