← back to Wolfgordon Crawl

scraper-utils.js

64 lines

// ============================================================================
// Minimal Mac2-local scraper utils for the Wolf Gordon crawl.
// Self-contained (only `pg`, no browser) — scrape-wolfgordon.js is pure HTTP.
// Targets the LOCAL dw_unified over the unix socket (peer auth as the OS user),
// the same DB the existing 5,377 wolf_gordon_catalog rows live in.
// Prod (Kamatera) sync is a separate, Steve-gated step — NOT this script.
// ============================================================================

const { Pool } = require('pg');

// Unix-socket / peer auth — mirrors `psql -d dw_unified` working on Mac2.
// Override with DATABASE_URL if ever needed.
const pool = process.env.DATABASE_URL
  ? new Pool({ connectionString: process.env.DATABASE_URL, max: 3 })
  : new Pool({ host: '/tmp', database: 'dw_unified', max: 3 });

async function upsertProduct(table, product) {
  if (!product.mfr_sku) return null;

  const fields = Object.keys(product).filter(k => product[k] !== undefined && product[k] !== null);
  const values = fields.map(k => product[k]);
  const placeholders = fields.map((_, i) => `$${i + 1}`);
  const updates = fields.filter(f => f !== 'mfr_sku').map(f => `${f} = EXCLUDED.${f}`);

  const sql = `
    INSERT INTO ${table} (${fields.join(', ')})
    VALUES (${placeholders.join(', ')})
    ON CONFLICT (mfr_sku) DO UPDATE SET
      ${updates.join(', ')},
      updated_at = NOW(),
      last_scraped = NOW()
    RETURNING id
  `;

  try {
    const res = await pool.query(sql, values);
    return res.rows[0]?.id;
  } catch (err) {
    if (err.message.includes('duplicate key')) return null;
    console.error(`  DB error for SKU ${product.mfr_sku}: ${err.message}`);
    return null;
  }
}

function wait(base = 300, jitter = 150) {
  const ms = base + Math.floor(Math.random() * jitter);
  return new Promise(r => setTimeout(r, ms));
}

async function getCatalogCount(table) {
  try {
    const res = await pool.query(`SELECT COUNT(*)::int AS c FROM ${table}`);
    return res.rows[0].c;
  } catch {
    return 0;
  }
}

async function closePool() {
  await pool.end();
}

module.exports = { pool, upsertProduct, wait, getCatalogCount, closePool };