[object Object]

← back to Naturaltextilewallpaper

render: route buy CTA + main-store anchor + card sample through /buy/:slug + close denylist gap (cowtan/designtex/knoll/brewster/york/fabricut/stout/hygge/designerwallcoverings); kill vendor-handle DOM leak (mirrors fleet a85eb02)

c2bd7bc11f3e90c719106ce1d461d1186c343722 · 2026-05-31 21:03:09 -0700 · Steve Abrams

Files touched

Diff

commit c2bd7bc11f3e90c719106ce1d461d1186c343722
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun May 31 21:03:09 2026 -0700

    render: route buy CTA + main-store anchor + card sample through /buy/:slug + close denylist gap (cowtan/designtex/knoll/brewster/york/fabricut/stout/hygge/designerwallcoverings); kill vendor-handle DOM leak (mirrors fleet a85eb02)
---
 server.js | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)

diff --git a/server.js b/server.js
index 9a8c26d..dc19b22 100644
--- a/server.js
+++ b/server.js
@@ -47,6 +47,9 @@ const VENDORS_DENYLIST = [
   'phillip jeffries', 'fromental', 'westport', 'breegan', 'les ensembliers',
   'roger thomas', 'stacy garcia', 'de gournay', 'romo', 'farrow and ball',
   'colefax and fowler', 'sanderson', 'morris and co', 'osborne and little',
+  // 2026-05-31 audit gap-close: these leaked even on scrubbed surfaces.
+  'cowtan', 'designtex', 'knoll', 'brewster', 'york', 'fabricut', 'stout',
+  'hygge', 'designerwallcoverings',
 ];
 // sort longest-first so multi-word vendors strip before their abbreviations
 // (e.g. "arte international" before bare "arte" → no "international" residue)
@@ -269,6 +272,17 @@ app.get('/api/facets', (req, res) => {
 });
 
 // --- Product detail page ------------------------------------------------
+// /buy/:slug — internal 302 to the main store. Resolves a vendor-redacted slug
+// to the real handle SERVER-SIDE so the raw vendor-bearing handle never appears
+// in customer-facing HTML (mirrors dw-domain-fleet a85eb02). ?sample → #sample.
+app.get('/buy/:slug', (req, res) => {
+  const key = req.params.slug;
+  const p = PRODUCTS.find(x => x.handle === key)
+         || PRODUCTS.find(x => redactVendorSlug(x.handle) === key);
+  if (!p) return res.status(404).send(page404());
+  res.redirect(302, `${DW}/products/${p.handle}` + (req.query.sample != null ? '#sample' : ''));
+});
+
 app.get('/p/:handle', (req, res) => {
   const key = req.params.handle;
   const p = PRODUCTS.find(x => x.handle === key)
@@ -645,7 +659,7 @@ function renderIndex(q, material, sort) {
         + '<span class="card-title">'+escHtml(p.title)+'</span>'
         + priceStr
         + '</div>'
-        + '<a href="https://designerwallcoverings.com/products/'+p.handle+'#sample" class="sample-cta" target="_blank" rel="noopener noreferrer" onclick="event.stopPropagation()">Order Memo Sample (free)</a>'
+        + '<a href="/buy/'+encodeURIComponent(p.slug||p.handle)+'?sample=1" class="sample-cta" target="_blank" rel="noopener noreferrer" onclick="event.stopPropagation()">Order Memo Sample (free)</a>'
         + '</a>';
     }
 
@@ -735,8 +749,9 @@ function renderIndex(q, material, sort) {
 }
 
 function renderDetail(p, matLabel) {
-  const dw_url = `${DW}/products/${p.handle}`;
-  const sample_url = `${dw_url}#sample`;
+  const buySlug = encodeURIComponent(redactVendorSlug(p.handle));
+  const dw_url = `/buy/${buySlug}`;
+  const sample_url = `/buy/${buySlug}?sample=1`;
   const images = p.images && p.images.length > 1 ? p.images : [p.image_url];
   return headHTML(
     `${p.title} — Free Memo Sample | Natural Textile Wallpaper`,

← f63b951 Add per-site favicon (kills /favicon.ico 404)  ·  back to Naturaltextilewallpaper  ·  Vendor-neutral image proxy via _shared/image-proxy: hero+det 2895d20 →