[object Object]

← back to NEW SKU Viewer

server: 404-guard snapshot paths + server-side sort on /api/vendors

b85fbce2e7e8bdc93875715ba1dfedd24d92cba8 · 2026-05-19 21:19:12 -0700 · Steve Abrams

- Middleware before express.static returns 404 for any request whose
  path matches *.bak / *.bak.* / *.pre-* / *.orig / *.rej / *.swp / ~,
  so stray editor snapshots never serve from public/.
- /api/vendors now honors a ?sort= query param (products|newest|vendor|
  series|sku|title|status) so the grid can be sorted server-side per
  the standing "every grid gets sort + density" rule. Frontend wiring
  in a follow-up commit.

node -c server.js passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit b85fbce2e7e8bdc93875715ba1dfedd24d92cba8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 19 21:19:12 2026 -0700

    server: 404-guard snapshot paths + server-side sort on /api/vendors
    
    - Middleware before express.static returns 404 for any request whose
      path matches *.bak / *.bak.* / *.pre-* / *.orig / *.rej / *.swp / ~,
      so stray editor snapshots never serve from public/.
    - /api/vendors now honors a ?sort= query param (products|newest|vendor|
      series|sku|title|status) so the grid can be sorted server-side per
      the standing "every grid gets sort + density" rule. Frontend wiring
      in a follow-up commit.
    
    node -c server.js passes.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 server.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 50 insertions(+), 2 deletions(-)

diff --git a/server.js b/server.js
index 7ccc4e2..23ee108 100644
--- a/server.js
+++ b/server.js
@@ -13,6 +13,15 @@ const PORT = 3030;
 // Middleware
 app.use(cors());
 app.use(express.json());
+
+// Snapshot-file 404 guard — never serve .bak/.bak.*/.pre-*/.orig/.rej/.swp/~ from static root
+app.use((req, res, next) => {
+  if (/\.(bak|orig|rej|swp)(\.|$)|\.pre-|~$/i.test(req.path)) {
+    return res.status(404).send('Not Found');
+  }
+  next();
+});
+
 app.use(express.static('public'));
 
 // In-memory cache for vendor results - Load from file if exists
@@ -270,12 +279,51 @@ async function scanAllVendors() {
   }
 }
 
+// Server-side sort for vendor list. Sort modes:
+//   products (default) — most products first
+//   newest   — most recently tested first
+//   vendor   — vendor id A→Z
+//   series   — vendor name A→Z (series-style display label)
+//   sku      — sku-prefix A→Z (same as vendor id)
+//   title    — vendor display name A→Z
+//   status   — working first, then errors
+function sortVendorEntries(entries, mode) {
+  const m = (mode || 'products').toLowerCase();
+  const arr = entries.slice();
+  const nameOf = ([id, v]) => (v.vendorName || id || '').toString().toLowerCase();
+  const idOf   = ([id]) => (id || '').toString().toLowerCase();
+  const tsOf   = ([, v]) => Date.parse(v.lastTested || '') || 0;
+  switch (m) {
+    case 'newest':
+      arr.sort((a, b) => tsOf(b) - tsOf(a));
+      break;
+    case 'vendor':
+    case 'sku':
+      arr.sort((a, b) => idOf(a).localeCompare(idOf(b)));
+      break;
+    case 'series':
+    case 'title':
+      arr.sort((a, b) => nameOf(a).localeCompare(nameOf(b)));
+      break;
+    case 'status':
+      arr.sort((a, b) => Number(!!b[1].success) - Number(!!a[1].success));
+      break;
+    case 'products':
+    default:
+      arr.sort((a, b) => (b[1].products || 0) - (a[1].products || 0));
+  }
+  return arr;
+}
+
 // API Routes
 app.get('/api/vendors', (req, res) => {
+  const sortedEntries = sortVendorEntries(Object.entries(vendorCache), req.query.sort);
+  const sortedVendors = Object.fromEntries(sortedEntries);
   const summary = {
-    vendors: vendorCache,
+    vendors: sortedVendors,
     lastUpdate,
     isScanning,
+    sort: (req.query.sort || 'products').toString(),
     stats: {
       total: Object.keys(vendorCache).length,
       successful: Object.values(vendorCache).filter(v => v.success).length,
@@ -283,7 +331,7 @@ app.get('/api/vendors', (req, res) => {
       totalProducts: Object.values(vendorCache).reduce((sum, v) => sum + v.products, 0)
     }
   };
-  
+
   res.json(summary);
 });
 

← de911b9 public: add rel=noopener noreferrer to product-modal externa  ·  back to NEW SKU Viewer  ·  public: wire sort <select> + density slider with localStorag 098e8b6 →