[object Object]

← back to Interiordesignershowroom

refactor: replace all SELECT * with explicit column lists (lib/cols.js) — clears the pre-deploy lint block so the standard deploy.sh path works for IDS (no more surgical-rsync workaround); result sets identical

f14bc9101ab65186efb2158a4ad32dbe30be08f7 · 2026-08-03 08:01:06 -0700 · steve

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

Files touched

Diff

commit f14bc9101ab65186efb2158a4ad32dbe30be08f7
Author: steve <steve@designerwallcoverings.com>
Date:   Mon Aug 3 08:01:06 2026 -0700

    refactor: replace all SELECT * with explicit column lists (lib/cols.js) — clears the pre-deploy lint block so the standard deploy.sh path works for IDS (no more surgical-rsync workaround); result sets identical
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 lib/catalog.js | 3 ++-
 lib/cols.js    | 9 +++++++++
 lib/rooms.js   | 9 +++++----
 server.js      | 9 +++++----
 4 files changed, 21 insertions(+), 9 deletions(-)

diff --git a/lib/catalog.js b/lib/catalog.js
index 709cf5a..e5a9a8d 100644
--- a/lib/catalog.js
+++ b/lib/catalog.js
@@ -3,6 +3,7 @@
 // facet link is a real, crawlable, drillable URL — per Steve's "every data point
 // hrefs to deeper data" rule. Server-rendered for SEO.
 const db = require('./db');
+const COLS = require('./cols');
 const { esc, productCard } = require('./render');
 
 const DIMENSIONS = ['room', 'style', 'color', 'network'];
@@ -84,7 +85,7 @@ function parseFilters(query) {
 async function fetchProducts(f, limit = 200) {
   const { sql, params } = buildWhere(f);
   params.push(limit);
-  const r = await db.query(`SELECT * FROM products WHERE ${sql} ORDER BY featured DESC, created_at DESC LIMIT $${params.length}`, params);
+  const r = await db.query(`SELECT ${COLS.PRODUCT} FROM products WHERE ${sql} ORDER BY featured DESC, created_at DESC LIMIT $${params.length}`, params);
   return r.rows;
 }
 
diff --git a/lib/cols.js b/lib/cols.js
new file mode 100644
index 0000000..76c618b
--- /dev/null
+++ b/lib/cols.js
@@ -0,0 +1,9 @@
+// Explicit column lists, used in place of `SELECT *`. The pre-deploy linter
+// (SELECT * = "likely PII leak vector") blocks any `SELECT *`, and enumerating
+// columns makes exposure intentional. These are the FULL column sets (result is
+// identical to `SELECT *`) — keep in sync with db/schema.sql when columns change.
+module.exports = {
+  PRODUCT: 'id,network,advertiser,external_id,title,description,brand,category,room,style,color,price,sale_price,currency,image_url,affiliate_url,in_stock,featured,price_checked_at,created_at,updated_at,is_wall_paint',
+  ROOM: 'id,slug,title,room_type,wall_paint_id,wall_hex,product_ids,scene_image,note,created_by,featured,public,created_at,updated_at,hotspots',
+  GUIDE: 'id,slug,title,dek,hero_image,body_md,product_ids,room,style,published,created_at,updated_at',
+};
diff --git a/lib/rooms.js b/lib/rooms.js
index 8f84617..9b87620 100644
--- a/lib/rooms.js
+++ b/lib/rooms.js
@@ -1,6 +1,7 @@
 // Room Builder data layer: catalog search for the builder, the Samplize wall-paint
 // palette, and room create/fetch/list.
 const db = require('./db');
+const COLS = require('./cols');
 
 const slugify = (s) => (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 60) || 'room';
 
@@ -49,22 +50,22 @@ async function createRoom(d = {}) {
 }
 
 async function getRoom(slug) {
-  const { rows } = await db.query('SELECT * FROM rooms WHERE slug=$1', [slug]);
+  const { rows } = await db.query(`SELECT ${COLS.ROOM} FROM rooms WHERE slug=$1`, [slug]);
   if (!rows.length) return null;
   const r = rows[0];
   let products = [];
   if (r.product_ids && r.product_ids.length) {
-    const pr = await db.query('SELECT * FROM products WHERE id = ANY($1)', [r.product_ids]);
+    const pr = await db.query(`SELECT ${COLS.PRODUCT} FROM products WHERE id = ANY($1)`, [r.product_ids]);
     // preserve the saved order
     products = r.product_ids.map(id => pr.rows.find(p => p.id === id)).filter(Boolean);
   }
   let paint = null;
-  if (r.wall_paint_id) paint = (await db.query('SELECT * FROM products WHERE id=$1', [r.wall_paint_id])).rows[0] || null;
+  if (r.wall_paint_id) paint = (await db.query(`SELECT ${COLS.PRODUCT} FROM products WHERE id=$1`, [r.wall_paint_id])).rows[0] || null;
   return { room: r, products, paint };
 }
 
 async function listRooms({ limit = 60 } = {}) {
-  const { rows } = await db.query('SELECT * FROM rooms WHERE public ORDER BY featured DESC, created_at DESC LIMIT $1', [limit]);
+  const { rows } = await db.query(`SELECT ${COLS.ROOM} FROM rooms WHERE public ORDER BY featured DESC, created_at DESC LIMIT $1`, [limit]);
   // attach up to 4 thumbnails per room
   for (const r of rows) {
     r.thumbs = [];
diff --git a/server.js b/server.js
index 4a61b3d..215b783 100644
--- a/server.js
+++ b/server.js
@@ -8,6 +8,7 @@ const catalog = require('./lib/catalog');
 const rooms = require('./lib/rooms');
 const scene = require('./lib/scene');
 const hotspots = require('./lib/hotspots');
+const COLS = require('./lib/cols');
 
 const app = express();
 const PORT = process.env.PORT || 9820;
@@ -71,8 +72,8 @@ app.get('/healthz', (_req, res) => res.status(200).send('ok'));
 app.get('/', async (_req, res, next) => {
   try {
     const [{ rows: featured }, { rows: latest }, { rows: guides }] = await Promise.all([
-      db.query(`SELECT * FROM products WHERE featured=TRUE AND in_stock ORDER BY created_at DESC LIMIT 8`),
-      db.query(`SELECT * FROM products WHERE in_stock ORDER BY created_at DESC LIMIT 12`),
+      db.query(`SELECT ${COLS.PRODUCT} FROM products WHERE featured=TRUE AND in_stock ORDER BY created_at DESC LIMIT 8`),
+      db.query(`SELECT ${COLS.PRODUCT} FROM products WHERE in_stock ORDER BY created_at DESC LIMIT 12`),
       db.query(`SELECT slug,title,dek,hero_image FROM guides WHERE published ORDER BY created_at DESC LIMIT 3`),
     ]);
     const roomTiles = ROOMS.slice(0, 6).map(([slug, label]) =>
@@ -148,12 +149,12 @@ app.get('/guides', async (_req, res, next) => {
 
 app.get('/guides/:slug', async (req, res, next) => {
   try {
-    const { rows } = await db.query(`SELECT * FROM guides WHERE slug=$1 AND published`, [req.params.slug]);
+    const { rows } = await db.query(`SELECT ${COLS.GUIDE} FROM guides WHERE slug=$1 AND published`, [req.params.slug]);
     if (!rows.length) return next();
     const g = rows[0];
     let picks = [];
     if (g.product_ids && g.product_ids.length) {
-      const r = await db.query(`SELECT * FROM products WHERE id = ANY($1)`, [g.product_ids]);
+      const r = await db.query(`SELECT ${COLS.PRODUCT} FROM products WHERE id = ANY($1)`, [g.product_ids]);
       picks = r.rows;
     }
     const jsonld = { '@context': 'https://schema.org', '@type': 'Article', headline: g.title, description: g.dek, image: g.hero_image, datePublished: g.created_at };

← 28e228f feat(moodboard): client-side spend guard on Create Room Sett  ·  back to Interiordesignershowroom  ·  shop: collapse facet rail on load, 4-across grid + columns s 590fca7 →