← back to Newmor Onboard
data/newmor-takedown.mjs
95 lines
import pg from 'pg';
import fs from 'fs';
import path from 'path';
const TOKEN = fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env','utf8')
.split('\n').find(l=>l.startsWith('SHOPIFY_ADMIN_TOKEN='))
.split('=').slice(1).join('=').trim().replace(/^['"]|['"]$/g,'');
const SHOP='designer-laboratory-sandbox.myshopify.com';
const API='2024-10';
const URL=`https://${SHOP}/admin/api/${API}/graphql.json`;
const MODE = process.argv[2] || 'canary'; // canary | rest
const ts = process.env.RUN_TS;
async function gql(query, variables){
for(let attempt=0; attempt<6; attempt++){
const r = await fetch(URL,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query,variables})});
const j = await r.json();
if(j.errors && JSON.stringify(j.errors).match(/THROTTLED|Throttled/)){ await sleep(2000*(attempt+1)); continue; }
// throttle-aware pacing
const ts2 = j?.extensions?.cost?.throttleStatus;
if(ts2 && ts2.currentlyAvailable < 300){ await sleep(1500); }
return j;
}
throw new Error('too many throttle retries');
}
const sleep = ms => new Promise(r=>setTimeout(r,ms));
const client = new pg.Client({host:'/tmp',database:'dw_unified'});
await client.connect();
// Load target rows: only live_price = 4.25
const {rows} = await client.query(`
select shopify_id, handle, live_sku, live_price, disposition
from newmor_sku_reconcile
where live_price = 4.25
order by shopify_id`);
const archiveDisp = new Set(['C_NO_CATALOG_COUNTERPART','JUNK']);
const draftDisp = new Set(['A_MATCHED','B_PATTERN_ONLY']);
function targetStatus(d){ return archiveDisp.has(d) ? 'ARCHIVED' : 'DRAFT'; }
// Build the working set
let working = rows;
if(MODE==='canary'){
// 3 from C, 1 JUNK, 3 A, 3 B => 2-3 from each bucket
const pick=(disp,n)=>rows.filter(r=>r.disposition===disp).slice(0,n);
working = [...pick('C_NO_CATALOG_COUNTERPART',3),...pick('JUNK',1),...pick('A_MATCHED',3),...pick('B_PATTERN_ONLY',3)];
} else if(MODE==='rest'){
// exclude anything already recorded in canary rollback file
const canaryFile = process.env.CANARY_FILE;
const done = new Set();
if(canaryFile && fs.existsSync(canaryFile)){
JSON.parse(fs.readFileSync(canaryFile,'utf8')).touched.forEach(t=>done.add(t.shopify_id));
}
working = rows.filter(r=>!done.has(r.shopify_id));
}
console.log(`MODE=${MODE} working set=${working.length} (of ${rows.length} @ $4.25)`);
const rollbackFile = `/Users/macstudio3/Projects/newmor-onboard/data/newmor-archive-rollback-${ts}.json`;
const touched=[]; const failures=[];
let archived=0, drafted=0;
for(const r of working){
const id = r.shopify_id;
const want = targetStatus(r.disposition);
// capture pre-change state
const pre = await gql(`query($id:ID!){ product(id:$id){ id title status publishedAt } }`,{id});
const p = pre?.data?.product;
if(!p){ failures.push({shopify_id:id, handle:r.handle, error:'product not found', pre}); continue; }
const preState = {shopify_id:id, handle:r.handle, disposition:r.disposition, prev_status:p.status, prev_publishedAt:p.publishedAt, new_status:want};
// mutate
const mut = await gql(`mutation($input:ProductInput!){ productUpdate(input:$input){ product{ id status publishedAt } userErrors{ field message } } }`,{input:{id, status:want}});
const ue = mut?.data?.productUpdate?.userErrors;
const after = mut?.data?.productUpdate?.product;
if((ue && ue.length) || !after){ failures.push({...preState, error: JSON.stringify(ue||mut)}); continue; }
preState.confirmed_status = after.status;
preState.confirmed_publishedAt = after.publishedAt;
touched.push(preState);
if(want==='ARCHIVED') archived++; else drafted++;
process.stdout.write(`.${want==='ARCHIVED'?'A':'D'}`);
await sleep(220); // stay well under bucket
}
console.log('');
fs.writeFileSync(rollbackFile, JSON.stringify({run_ts:ts, mode:MODE, mechanism:{archive:'status=ARCHIVED (C+JUNK)', hold:'status=DRAFT (A+B)'}, touched, failures}, null, 2));
console.log(`archived=${archived} drafted=${drafted} failures=${failures.length}`);
console.log(`rollback file: ${rollbackFile}`);
if(failures.length) console.log('FAILURES:', JSON.stringify(failures,null,2));
await client.end();
export {};