[object Object]

← back to Designer Wallcoverings

auto-save: 2026-07-31T12:27:46 (1 files) — DW-Agents/vendor-command-center/server.js

2673366cb02c41881fd248b18bd6bcc98790b103 · 2026-07-31 12:27:54 -0700 · Steve Abrams

Files touched

Diff

commit 2673366cb02c41881fd248b18bd6bcc98790b103
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 31 12:27:54 2026 -0700

    auto-save: 2026-07-31T12:27:46 (1 files) — DW-Agents/vendor-command-center/server.js
---
 DW-Agents/vendor-command-center/server.js | 112 ++++++++++++++++++++++++++++++
 1 file changed, 112 insertions(+)

diff --git a/DW-Agents/vendor-command-center/server.js b/DW-Agents/vendor-command-center/server.js
index 09f16455..ba0d97dc 100644
--- a/DW-Agents/vendor-command-center/server.js
+++ b/DW-Agents/vendor-command-center/server.js
@@ -2596,6 +2596,84 @@ app.get('/api/vendors/:code/products', async (req, res) => {
   }
 });
 
+// --- API: Live Shopify verify — mirror-vs-live drift check (TK-10084) ---
+// READ-ONLY (productsCount only). Click-to-verify from the Vendors tab; never auto-run.
+app.get('/api/vendors/:code/live-verify', async (req, res) => {
+  try {
+    const code = req.params.code;
+    const { rows: vendorRows } = await pool.query(
+      'SELECT vendor_name, vendor_code FROM vendor_registry WHERE vendor_code = $1',
+      [code]
+    );
+    if (!vendorRows.length) return res.status(404).json({ success: false, error: 'Vendor not found' });
+
+    // Same pattern resolution as syncVendorCounts (map first, registry-name fallback)
+    const patterns = VENDOR_NAME_MAP[code] || (vendorRows[0].vendor_name ? [vendorRows[0].vendor_name + '%'] : null);
+    if (!patterns || !patterns.length) {
+      return res.status(400).json({ success: false, error: 'No Shopify vendor mapping configured' });
+    }
+
+    // Mirror side: same ILIKE patterns as syncVendorCounts, per-status
+    const conditions = patterns.map((_, i) => `vendor ILIKE $${i + 1}`).join(' OR ');
+    const mirRes = await pool.query(
+      `SELECT COUNT(CASE WHEN UPPER(status) = 'ACTIVE' THEN 1 END) AS active_cnt,
+              COUNT(CASE WHEN UPPER(status) = 'DRAFT' THEN 1 END) AS draft_cnt,
+              COUNT(CASE WHEN UPPER(status) = 'ARCHIVED' THEN 1 END) AS arch_cnt
+       FROM shopify_products WHERE ${conditions}`,
+      patterns
+    );
+    const mirror = {
+      active: parseInt(mirRes.rows[0].active_cnt) || 0,
+      draft: parseInt(mirRes.rows[0].draft_cnt) || 0,
+      archived: parseInt(mirRes.rows[0].arch_cnt) || 0
+    };
+
+    // Live side: productsCount per exact pattern base per status, summed across bases.
+    // Exact vendor-string match may undercount vs the mirror's ILIKE '<base>%' wildcards.
+    const bases = [...new Set(patterns.map(p => p.replace(/%+$/, '').trim()).filter(Boolean))];
+    const live = { active: 0, draft: 0, archived: 0 };
+    const gqlUrl = `https://${SHOPIFY_DOMAIN}/admin/api/2024-10/graphql.json`;
+    for (const base of bases) {
+      for (const status of ['active', 'draft', 'archived']) {
+        const q = `vendor:'${base.replace(/'/g, "\\'")}' status:${status}`;
+        const controller = new AbortController();
+        const timer = setTimeout(() => controller.abort(), 10000);
+        try {
+          const resp = await fetch(gqlUrl, {
+            method: 'POST',
+            headers: {
+              'Content-Type': 'application/json',
+              'X-Shopify-Access-Token': process.env.SHOPIFY_ADMIN_TOKEN
+            },
+            body: JSON.stringify({ query: `{ productsCount(query: ${JSON.stringify(q)}) { count } }` }),
+            signal: controller.signal
+          });
+          const j = await resp.json();
+          live[status] += (j.data && j.data.productsCount && j.data.productsCount.count) || 0;
+        } finally {
+          clearTimeout(timer);
+        }
+        await new Promise(r => setTimeout(r, 300)); // pace Shopify calls
+      }
+    }
+
+    const drift = live.active !== mirror.active || live.draft !== mirror.draft || live.archived !== mirror.archived;
+    res.json({
+      success: true,
+      vendor: vendorRows[0].vendor_name,
+      code,
+      patterns: bases,
+      live,
+      mirror,
+      drift,
+      note: 'live = exact vendor-string productsCount per pattern base; may undercount vs mirror ILIKE wildcard patterns',
+      checkedAt: new Date().toISOString()
+    });
+  } catch (err) {
+    res.status(500).json({ success: false, error: err.message });
+  }
+});
+
 // --- Catalog Browse API (infinite scroll + phase progress) ---
 app.get('/api/catalog-browse', async (req, res) => {
   try {
@@ -7286,6 +7364,40 @@ function renderTable(list) {
     shopTd.textContent = Number(shopCount).toLocaleString();
     if (shopCount > 0) shopTd.style.color = '#60a5fa';
     else shopTd.style.color = 'var(--text-muted)';
+    // Live-verify button (TK-10084): click-to-check mirror vs live Shopify productsCount
+    var lvBtn = document.createElement('button');
+    lvBtn.textContent = '✓';
+    lvBtn.title = 'Verify against live Shopify (productsCount, read-only)';
+    lvBtn.style.cssText = 'margin-left:6px;padding:0 5px;font-size:10px;cursor:pointer;background:transparent;border:1px solid var(--border);border-radius:4px;color:var(--text-muted);';
+    lvBtn.addEventListener('click', (function(code, btn) { return function(e) {
+      e.stopPropagation();
+      btn.disabled = true; btn.textContent = '…';
+      fetch('/api/vendors/' + code + '/live-verify').then(function(r) { return r.json(); }).then(function(j) {
+        var badge = document.createElement('span');
+        badge.style.cssText = 'margin-left:6px;padding:1px 6px;border-radius:8px;font-size:10px;font-weight:700;white-space:nowrap;';
+        if (!j.success) {
+          badge.textContent = 'ERR';
+          badge.style.background = '#7f1d1d'; badge.style.color = '#fca5a5';
+          badge.title = j.error || 'verify failed';
+        } else {
+          var delta = Math.abs(j.live.active - j.mirror.active) + Math.abs(j.live.draft - j.mirror.draft) + Math.abs(j.live.archived - j.mirror.archived);
+          badge.title = 'live active/draft/archived: ' + j.live.active + '/' + j.live.draft + '/' + j.live.archived +
+            ' | mirror: ' + j.mirror.active + '/' + j.mirror.draft + '/' + j.mirror.archived;
+          if (j.drift) {
+            badge.textContent = 'DRIFT ' + delta.toLocaleString();
+            badge.style.background = '#78350f'; badge.style.color = '#fbbf24';
+          } else {
+            badge.textContent = 'LIVE ✓';
+            badge.style.background = '#14532d'; badge.style.color = '#4ade80';
+          }
+        }
+        btn.replaceWith(badge);
+      }).catch(function(err) {
+        btn.disabled = false; btn.textContent = '✓';
+        btn.title = 'verify failed: ' + err;
+      });
+    }; })(v.vendor_code, lvBtn));
+    shopTd.appendChild(lvBtn);
     tr.appendChild(shopTd);
 
     // Not on Shopify (gap)

← cf9719e9 auto-save: 2026-07-31T11:57:35 (1 files) — DW-Agents/vendor-  ·  back to Designer Wallcoverings  ·  auto-save: 2026-07-31T14:58:42 (3 files) — DW-Agents/vendor- 73a7b19c →