← back to Goldleafwallpaper

scripts/build-products.js

89 lines

#!/usr/bin/env node
/**
 * build-products.js — regenerate data/products.json for goldleafwallpaper.com
 *
 * The site is a metal-leaf micro-storefront: it must show ONLY the Renaissance
 * Metal Leaf line (Phillipe Romano, dw_sku prefix "RML-") — the gold & silver
 * leaf wallcoverings — and nothing else. It previously carried a 1,957-product
 * catalog dump from a dozen unrelated vendors, which was wrong.
 *
 * Source of truth: the local dw_unified mirror (host=/tmp socket). We read the
 * ACTIVE RML- products only (ARCHIVED are excluded — never resurrect archived
 * SKUs), then shape each row into the flat object the front end consumes.
 *
 * Every RML- product ships the same DW title ("Phillipe Romano - Renaissance
 * Metal Leaf"), so we derive a distinct, honest per-card title from the real
 * catalog tags:  "<finish> Leaf — <colorway>"  (e.g. "Gold Leaf — Brass").
 * finish comes from the Gold/Silver/Bronze tag; colorway from the "color:X" tag.
 * The linked DW product page and handle are left untouched.
 *
 * Usage:  node scripts/build-products.js
 */
const { execFileSync } = require('child_process');
const fs = require('fs');
const path = require('path');

const OUT = path.join(__dirname, '..', 'data', 'products.json');

// Pull the raw ACTIVE RML- rows as one JSON array straight from Postgres.
const SQL = `
  select coalesce(json_agg(row_to_json(t) order by t.dw_sku), '[]'::json)
  from (
    select dw_sku, mfr_sku, title, vendor, handle, product_type,
           image_url, retail_price, price, tags
    from shopify_products
    where dw_sku ilike 'RML-%' and status = 'ACTIVE'
  ) t;
`;

const raw = execFileSync('psql', ['-h', '/tmp', 'dw_unified', '-tAc', SQL], {
  encoding: 'utf8', maxBuffer: 64 * 1024 * 1024,
});
const rows = JSON.parse(raw.trim());

// tags arrive as the Postgres text form: {"Architectural","color:Brass",...}
function parseTags(tagStr) {
  if (!tagStr) return [];
  return String(tagStr)
    .replace(/^\{|\}$/g, '')
    .split(',')
    .map((s) => s.trim().replace(/^"|"$/g, ''))
    .filter(Boolean);
}

const FINISH_ORDER = ['Silver', 'Gold', 'Bronze']; // Silver first so silver-tagged win over a stray Gold

function deriveTitle(tags) {
  const finish = FINISH_ORDER.find((f) => tags.includes(f));
  const colorTag = tags.find((t) => /^color:/i.test(t));
  const colorway = colorTag ? colorTag.replace(/^color:/i, '').trim() : '';
  const finishLabel = finish ? `${finish} Leaf` : 'Renaissance Metal Leaf';
  // Separator is a middot, NOT an em-dash: the fleet's api-vendor-redact
  // scrubTitle() splits titles on | – — and drops trailing segments (it strips
  // "Vendor — Pattern" leakage), which would eat the colorway on sister sites.
  return colorway ? `${finishLabel} · ${colorway}` : finishLabel;
}

const products = rows.map((r) => {
  const tags = parseTags(r.tags);
  return {
    handle: r.handle,
    title: deriveTitle(tags),
    vendor: r.vendor, // "Phillipe Romano"
    product_type: r.product_type || 'Wallcovering',
    image_url: r.image_url || '',
    retail_price: r.retail_price || '', // quote-only metal-leaf line
    mfr_sku: r.mfr_sku || '',
    dw_sku: r.dw_sku,
    tags: tags.join(', '), // front end searches/filters a lowercased string
    material: 'Metal Leaf',
  };
});

fs.writeFileSync(OUT, JSON.stringify(products));

const finishCount = (needle) =>
  products.filter((p) => p.title.startsWith(needle)).length;
console.log(`Wrote ${products.length} RML- products → ${path.relative(process.cwd(), OUT)}`);
console.log(`  Gold Leaf: ${finishCount('Gold Leaf')}  Silver Leaf: ${finishCount('Silver Leaf')}  Bronze Leaf: ${finishCount('Bronze Leaf')}`);