← back to Interiordesignershowroom

scripts/seed.js

77 lines

// Seeds a realistic sample catalog so the showroom is fully browsable BEFORE any
// affiliate account is approved. Every row still flows through normalizeProduct(),
// so the seed exercises the exact same path the real adapters will.
//
// NOTE: affiliate_url values here are placeholder go-throughs — they become real
// tracked deep links once the network accounts + advertiser approvals land.
const db = require('../lib/db');
const { normalizeProduct, UPSERT_SQL, upsertParams } = require('../lib/normalize');

const IMG = (seed) => `https://picsum.photos/seed/ids-${seed}/600/600`;

const SAMPLE = [
  { network:'cj', advertiser:'Wayfair', external_id:'wf-1001', title:'Emmett Tufted Velvet Sofa, 84"', brand:'Kelly Clarkson Home', price:1299, sale_price:989, image_url:IMG(1), featured:true, category:'Living Room > Sofas' },
  { network:'cj', advertiser:'Lamps Plus', external_id:'lp-2003', title:'Arteriors Brass Arc Floor Lamp', brand:'Arteriors', price:640, image_url:IMG(2), category:'Lighting > Floor Lamps' },
  { network:'cj', advertiser:'Ballard Designs', external_id:'bd-3007', title:'Suzanne Kasler Linen Slipcover Armchair', brand:'Ballard', price:899, image_url:IMG(3), featured:true, category:'Living Room > Chairs' },
  { network:'cj', advertiser:'Frontgate', external_id:'fg-4012', title:'Marble & Brass Round Coffee Table', brand:'Frontgate', price:1150, image_url:IMG(4), category:'Living Room > Coffee Tables' },
  { network:'amazon', advertiser:'Amazon', external_id:'B0MODERN01', title:'Mid-Century Modern Walnut Nightstand with Drawer', brand:'Nathan James', price:129, sale_price:98, image_url:IMG(5), featured:true, category:'Bedroom > Nightstands' },
  { network:'amazon', advertiser:'Amazon', external_id:'B0RUG0002', title:'Hand-Woven Jute Area Rug 8x10, Natural', brand:'nuLOOM', price:214, image_url:IMG(6), category:'Decor > Rugs' },
  { network:'amazon', advertiser:'Amazon', external_id:'B0LAMP003', title:'Ceramic Table Lamp Set of 2, Oatmeal Linen Shade', brand:'JONATHAN Y', price:89, image_url:IMG(7), category:'Lighting > Table Lamps' },
  { network:'amazon', advertiser:'Amazon', external_id:'B0DESK004', title:'Solid Oak Writing Desk, Scandinavian', brand:'Grain Wood', price:399, image_url:IMG(8), category:'Office > Desks' },
  { network:'amazon', advertiser:'Amazon', external_id:'B0MIRR005', title:'Round Brass Wall Mirror 30", Art Deco', brand:'Kate and Laurel', price:119, image_url:IMG(9), category:'Decor > Mirrors' },
  { network:'rakuten', advertiser:'Serena & Lily', external_id:'sl-5001', title:'Riviera Side Chair, Coastal Rattan', brand:'Serena & Lily', price:298, image_url:IMG(10), featured:true, category:'Dining > Chairs' },
  { network:'rakuten', advertiser:'Design Within Reach', external_id:'dwr-5008', title:'Eames Molded Plastic Rocker', brand:'Herman Miller', price:545, image_url:IMG(11), category:'Living Room > Chairs' },
  { network:'rakuten', advertiser:'West Elm', external_id:'we-5015', title:'Mid-Century Media Console, Acorn 68"', brand:'West Elm', price:899, sale_price:719, image_url:IMG(12), category:'Living Room > Media Consoles' },
  { network:'rakuten', advertiser:'CB2', external_id:'cb2-5020', title:'Peekaboo Acrylic Coffee Table', brand:'CB2', price:349, image_url:IMG(13), category:'Living Room > Coffee Tables' },
  { network:'shareasale', advertiser:'Ruggable', external_id:'rg-6001', title:'Washable Persian Rug, Blue 9x12', brand:'Ruggable', price:399, image_url:IMG(14), category:'Decor > Rugs' },
  { network:'shareasale', advertiser:'Article', external_id:'art-6010', title:'Sven Charme Tan Leather Sofa, 88"', brand:'Article', price:1799, featured:true, image_url:IMG(15), category:'Living Room > Sofas' },
  { network:'shareasale', advertiser:'Burrow', external_id:'bur-6018', title:'Nomad Sectional, Forest Green Velvet', brand:'Burrow', price:2395, image_url:IMG(16), category:'Living Room > Sectionals' },
  { network:'shareasale', advertiser:'Lulu and Georgia', external_id:'lg-6025', title:'Rattan Headboard, Queen', brand:'Lulu and Georgia', price:849, image_url:IMG(17), category:'Bedroom > Headboards' },
  { network:'cj', advertiser:'Pottery Barn', external_id:'pb-7001', title:'Farmhouse Reclaimed Wood Dining Table, 84"', brand:'Pottery Barn', price:1499, image_url:IMG(18), category:'Dining > Tables' },
  { network:'amazon', advertiser:'Amazon', external_id:'B0OUT0006', title:'Acacia Wood Adirondack Chair, Set of 2', brand:'Christopher Knight', price:279, image_url:IMG(19), category:'Outdoor > Seating' },
  { network:'rakuten', advertiser:'AllModern', external_id:'am-5030', title:'Glass Globe Pendant Light, Brass', brand:'AllModern', price:189, image_url:IMG(20), category:'Lighting > Pendants' },
  { network:'amazon', advertiser:'Amazon', external_id:'B0DEC0007', title:'Set of 3 Ceramic Vases, Neutral Matte', brand:'Bloomingville', price:64, image_url:IMG(21), category:'Decor > Vases' },
  { network:'shareasale', advertiser:'Boll & Branch', external_id:'bb-6030', title:'Signature Hemmed Bedding Set, White', brand:'Boll & Branch', price:299, image_url:IMG(22), category:'Bedroom > Bedding' },
  { network:'cj', advertiser:'Home Depot', external_id:'hd-7010', title:'Industrial Metal & Wood Bookcase, 5-Shelf', brand:'Home Decorators', price:229, sale_price:184, image_url:IMG(23), category:'Office > Bookcases' },
  { network:'rakuten', advertiser:'Anthropologie', external_id:'anth-5040', title:'Primrose Velvet Accent Chair, Blush', brand:'Anthropologie', price:998, image_url:IMG(24), category:'Living Room > Chairs' },
];

const GUIDES = [
  { slug:'best-velvet-sofas', title:'The 6 Best Velvet Sofas for a Designer Living Room', dek:'Our editors on which velvet sofas actually hold up — and how to shop the look for less.', hero_image:IMG(1), published:true, room:'living-room', style:'glam',
    external_refs:['wf-1001','art-6010','bur-6018'],
    body_md:`## Why velvet, and why now\n\nVelvet reads luxe because it plays with light — the pile shifts tone as you move past it. That depth is what makes a room feel *designed* rather than assembled.\n\n### What we looked for\n\n**Frame + fill.** A kiln-dried hardwood frame and high-resilience foam are the difference between a sofa that looks great for six months and one that looks great for six years.\n\n**Color that hides life.** Forest green, navy, and cognac hide wear far better than pale blush — choose accordingly if you have kids or pets.\n\n### Our picks\n\nBelow are the pieces our editors would put in their own homes, spanning entry-level to investment.` },
  { slug:'shop-the-look-mid-century-bedroom', title:'Shop the Look: A Warm Mid-Century Bedroom Under $2,000', dek:'A complete, cohesive bedroom — bed, nightstands, lighting, and layers — sourced from our favorite brands.', hero_image:IMG(5), published:true, room:'bedroom', style:'mid-century',
    external_refs:['B0MODERN01','B0LAMP003','lg-6025','bb-6030'],
    body_md:`## The formula\n\nMid-century works because it commits to warm wood tones and clean, tapered lines. Keep the palette to walnut + oatmeal + one accent and the room reads intentional.\n\n### Start with the anchor\n\nThe bed and nightstands set the wood tone for everything else. Match undertones — walnut wants warm brass, not cool chrome.\n\n### Layer the light\n\nSkip the single overhead. A pair of ceramic table lamps at the nightstands plus one warm floor lamp gives you the three light sources every designer bedroom has.` },
];

async function main() {
  await db.query('SELECT 1'); // fail fast if DB unreachable
  const idByExt = {};
  let ok = 0;
  for (const raw of SAMPLE) {
    // sample rows carry no real tracked link yet — synthesize a placeholder destination
    const withUrl = { ...raw, affiliate_url: raw.affiliate_url || `https://example.com/${raw.network}/${raw.external_id}` };
    const norm = normalizeProduct(withUrl, raw.network);
    if (!norm) { console.warn('skipped (failed normalize):', raw.title); continue; }
    const { rows } = await db.query(UPSERT_SQL, upsertParams(norm));
    idByExt[raw.external_id] = rows[0].id;
    ok++;
  }
  console.log(`seeded ${ok} products`);

  for (const g of GUIDES) {
    const product_ids = (g.external_refs || []).map((e) => idByExt[e]).filter(Boolean);
    await db.query(
      `INSERT INTO guides (slug,title,dek,hero_image,body_md,product_ids,room,style,published)
       VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)
       ON CONFLICT (slug) DO UPDATE SET title=EXCLUDED.title,dek=EXCLUDED.dek,hero_image=EXCLUDED.hero_image,
         body_md=EXCLUDED.body_md,product_ids=EXCLUDED.product_ids,published=EXCLUDED.published,updated_at=now()`,
      [g.slug, g.title, g.dek, g.hero_image, g.body_md, product_ids, g.room, g.style, g.published]);
  }
  console.log(`seeded ${GUIDES.length} guides`);
  await db.pool.end();
}

main().catch((e) => { console.error(e); process.exit(1); });