← back to Greenland Onboard

scripts/localize-images.mjs

40 lines

#!/usr/bin/env node
// Download all cork product images LOCALLY (never hotlink a remote CDN — Steve's standing rule)
// and re-export public/products.json with local /img/<dw_sku>.jpg paths.
import { execSync } from 'node:child_process';
import { mkdirSync, writeFileSync, existsSync, statSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const IMGDIR = join(ROOT, 'public', 'img');
mkdirSync(IMGDIR, { recursive: true });
const DB = process.env.DATABASE_URL || 'postgresql:///dw_unified?host=/tmp&user=stevestudio2';

const rows = JSON.parse(execSync(`psql "${DB}" -tAc "select coalesce(json_agg(json_build_object('gl',gl_id,'sku',dw_sku,'name',pattern_name,'color',color_name,'coll',collection,'width',width,'url',image_url,'purl',product_url) order by gl_id desc)::text,'[]') from greenland_catalog"`, { encoding: 'utf8', maxBuffer: 128e6 }).trim());

let dl = 0, skip = 0, fail = 0;
for (const r of rows) {
  const local = `img/${r.sku}.jpg`;
  const dest = join(ROOT, 'public', local);
  r.image_local = local;
  if (existsSync(dest) && statSync(dest).size > 1000) { skip++; continue; }
  if (!r.url) { fail++; continue; }
  try {
    const resp = await fetch(r.url, { signal: AbortSignal.timeout(30000) });
    if (!resp.ok) { fail++; continue; }
    const buf = Buffer.from(await resp.arrayBuffer());
    if (buf.length < 1000) { fail++; continue; }
    writeFileSync(dest, buf);
    dl++; if (dl % 25 === 0) console.log(`  ...downloaded ${dl}`);
  } catch { fail++; }
}
console.log(`images: downloaded=${dl} cached=${skip} fail=${fail}`);

// re-export products.json with the field names the viewer expects (p.image / p.pattern / p.color / p.url)
const mfrRows = JSON.parse(execSync(`psql "${DB}" -tAc "select coalesce(json_agg(json_build_object('gl',gl_id,'mfr',trim(mfr_sku)))::text,'[]') from greenland_catalog"`, { encoding: 'utf8', maxBuffer: 64e6 }).trim());
const mfrByGl = new Map(mfrRows.map(x => [x.gl, x.mfr]));
const out = rows.map(r => ({ gl_id: r.gl, dw_sku: r.sku, mfr_sku: mfrByGl.get(r.gl) || '', pattern: r.name, color: r.color, collection: r.coll, width: r.width, image: r.image_local, url: r.purl }));
writeFileSync(join(ROOT, 'public', 'products.json'), JSON.stringify(out));
console.log(`products.json re-exported (${out.length}) with local /img paths`);