← back to Rentv 2026
Cody-gate: posts public-status now published|live in lockstep with /api/search (was published-only → a future 'live' post would 404 on its page while showing in search); cross-ref comment on the parallel search filter
1fd2ec0767eef3d4540c7b7ae6c7dfb3a8fbdfa8 · 2026-08-05 14:31:20 -0700 · Steve Abrams
Files touched
Diff
commit 1fd2ec0767eef3d4540c7b7ae6c7dfb3a8fbdfa8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Aug 5 14:31:20 2026 -0700
Cody-gate: posts public-status now published|live in lockstep with /api/search (was published-only → a future 'live' post would 404 on its page while showing in search); cross-ref comment on the parallel search filter
---
server.js | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/server.js b/server.js
index 093cc1c9..05ed5e2c 100644
--- a/server.js
+++ b/server.js
@@ -459,8 +459,10 @@ app.get('/api/posts', (req, res) => {
let a = readPosts();
// Non-admins ONLY ever see published posts — drafts (outside-wire, copyright-sensitive per
// Option 1) must never be served to a user-tier caller, even one past the site Basic-auth.
+ // "Public" = published OR live — kept in lockstep with /api/search's posts filter.
+ const isPublic = (p) => p.status === 'published' || p.status === 'live';
if (req.role === 'admin') { if (req.query.status) a = a.filter(p => p.status === req.query.status); }
- else a = a.filter(p => p.status === 'published');
+ else a = a.filter(isPublic);
a.sort((x, y) => (y.created_at || '').localeCompare(x.created_at || ''));
res.json({ count: a.length, items: a });
});
@@ -468,7 +470,8 @@ app.get('/api/posts/:id', (req, res) => {
const p = readPosts().find(x => x.id === req.params.id);
if (!p) return res.status(404).json({ error: 'not found' });
// A draft is invisible to non-admins — 404 as if it doesn't exist (no guessing draft ids).
- if (p.status !== 'published' && req.role !== 'admin') return res.status(404).json({ error: 'not found' });
+ const isPublic = p.status === 'published' || p.status === 'live';
+ if (!isPublic && req.role !== 'admin') return res.status(404).json({ error: 'not found' });
res.json(p);
});
app.post('/api/posts', adminOnly, async (req, res) => {
@@ -1427,7 +1430,9 @@ app.get('/api/search', (req, res) => {
const blob = [v.title, v.desc, v.cat].join(' ');
if (hit(blob)) push('Videos', { title: v.title, sub: v.cat || 'Video', href: v.view_url || v.embed || '#', image: v.thumb || '', s: score(blob) });
});
- // Editorial posts
+ // Editorial posts — direct posts.json read (NOT via /api/posts). The status filter below
+ // MUST stay in sync with the user-tier gate in app.get('/api/posts'): only 'published'/'live'
+ // are ever exposed here, so drafts never leak into search results. Keep both in lockstep.
asArr(readJSON('posts.json', [])).forEach((p) => {
if (p.status && p.status !== 'published' && p.status !== 'live') return;
const blob = [p.title, p.dek, p.author, p.cat].join(' ');
← f40b0bc9 PR intel: linkedin-enrich prioritizes media/PR contacts (org
·
back to Rentv 2026
·
PR intel: CRM name/title hygiene tool (byline + trailing-pun 1663154b →