← back to Dw Image Shrink

phase4-verify.js

53 lines

// Phase 4 — verification + the DEFINITIVE cap gate.
// (1) Ledger rollups: counts by state + total bytes reclaimed.
// (2) Scratch upload gate: create/reuse a DRAFT "ZZ Storage Probe" product, POST
//     a real ~800px test JPEG, and read the status. 200 = cap CLEARED (uploads
//     resume). 413 = still AT CAP. Deletes the test image after (keeps the probe
//     product for reuse). Pass --gate-only to skip rollups.
import fs from 'fs';
import os from 'os';
import https from 'https';
import pg from 'pg';
import { execFileSync } from 'child_process';
const SHOP='designer-laboratory-sandbox.myshopify.com';
const ATOK=fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env','utf8').split('\n').find(l=>l.startsWith('SHOPIFY_ADMIN_TOKEN=')).split('=').slice(1).join('=').replace(/["' ]/g,'');
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
function raw(method,path,body){return new Promise((res,rej)=>{const data=body?JSON.stringify(body):null;const req=https.request({method,host:SHOP,path,headers:{'X-Shopify-Access-Token':ATOK,'Content-Type':'application/json',...(data?{'Content-Length':Buffer.byteLength(data)}:{})}},r=>{let d='';r.on('data',c=>d+=c);r.on('end',()=>res({status:r.statusCode,json:(()=>{try{return JSON.parse(d)}catch(e){return d}})()}));});req.on('error',rej);if(data)req.write(data);req.end();});}
async function api(m,p,b){for(let a=0;a<6;a++){const r=await raw(m,p,b);if(r.status===429){await sleep(3000);continue;}return r;}return{status:429,json:{}};}

if(!process.argv.includes('--gate-only')){
  const db=new pg.Client({host:'/tmp',database:'dw_unified'}); await db.connect();
  const {rows}=await db.query("SELECT state,count(*) n, sum(coalesce(orig_bytes,0)-coalesce(new_bytes,0)) FILTER(WHERE state='done') recl FROM img_shrink_ledger GROUP BY state ORDER BY state");
  console.log('=== LEDGER ROLLUP ===');
  let recl=0; for(const r of rows){ console.log(`  ${r.state.padEnd(16)} ${r.n}`); if(r.recl) recl=+r.recl; }
  console.log(`  reclaimed(done): ${(recl/1073741824).toFixed(2)} GB`);
  const {rows:[a]}=await db.query("SELECT count(*) FILTER(WHERE state='imageless_alert') n FROM img_shrink_ledger");
  if(+a.n>0) console.log(`  ⚠️ imageless_alert rows: ${a.n} (products left imageless — investigate!)`);
  await db.end();
}

// ---- scratch upload gate ----
// generate a real ~800x800 test JPEG
const fp=`${os.tmpdir()}/dw_storage_probe.jpg`;
execFileSync('python3',['-c',`from PIL import Image; import random; im=Image.new('RGB',(800,800),(120,120,120)); [im.putpixel((random.randint(0,799),random.randint(0,799)),(random.randint(0,255),0,0)) for _ in range(4000)]; im.save('${fp}','JPEG',quality=82)`]);
const b64=fs.readFileSync(fp).toString('base64');

// find or create the probe product
let probe;
const search=await api('GET','/admin/api/2024-10/products.json?limit=1&title=ZZ%20Storage%20Probe&fields=id,title');
if(search.json.products&&search.json.products.length){ probe=search.json.products[0].id; }
else{ const c=await api('POST','/admin/api/2024-10/products.json',{product:{title:'ZZ Storage Probe',status:'draft',published:false}}); probe=c.json.product&&c.json.product.id; }
if(!probe){ console.log('GATE: could not create/find probe product'); process.exit(1); }

const up=await api('POST',`/admin/api/2024-10/products/${probe}/images.json`,{image:{attachment:b64,filename:'storage-probe.jpg'}});
console.log('\n=== CAP GATE (scratch upload) ===');
if(up.status<300 && up.json.image){
  console.log('✅ 200 — UPLOAD SUCCEEDED → cap is CLEARED, uploads have resumed.');
  await api('DELETE',`/admin/api/2024-10/products/${probe}/images/${up.json.image.id}.json`); // clean up test image
  process.exit(0);
}else{
  const msg=JSON.stringify(up.json).slice(0,160);
  console.log(`❌ ${up.status} — UPLOAD FAILED → still AT CAP. ${msg}`);
  process.exit(2);
}