← 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 {};