← 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
M lib/catalog.jsA lib/cols.jsM lib/rooms.jsM server.js
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 →