← back to Ventura Corridor
iter 143: feedback POST → George email Steve on typo/correction/tip/complaint (skip praise/note)
d1f488369f003f835635a8194bb7b51d8873798f · 2026-05-06 19:06:00 -0700 · SteveStudio2
Files touched
Diff
commit d1f488369f003f835635a8194bb7b51d8873798f
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 6 19:06:00 2026 -0700
iter 143: feedback POST → George email Steve on typo/correction/tip/complaint (skip praise/note)
---
src/server/index.ts | 48 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 47 insertions(+), 1 deletion(-)
diff --git a/src/server/index.ts b/src/server/index.ts
index f26a0ad..aad119d 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -3104,6 +3104,7 @@ ${b.notes ? `<div style="margin-top:24px;padding:14px 18px;background:rgba(184,1
});
// Reader feedback intake — typo reports, business-owner corrections, story tips.
+// Inserts to PG + fires a George Gmail ping so Steve sees it inbox-side.
app.post('/api/feedback', express.json(), async (req, res) => {
try {
const b = req.body || {};
@@ -3116,7 +3117,52 @@ app.post('/api/feedback', express.json(), async (req, res) => {
VALUES ($1, $2, $3, $4, $5) RETURNING id`,
[b.feature_id || null, kind, b.body, b.contact || null, b.source_path || null]
);
- res.json({ ok: true, id: r.rows[0].id });
+ const fbId = r.rows[0].id;
+
+ // Notify Steve only for real correction-class kinds; skip pure "praise"/"note" to avoid inbox noise.
+ const NOTIFY_KINDS = new Set(['typo', 'correction', 'tip', 'complaint']);
+ if (NOTIFY_KINDS.has(kind)) {
+ // Look up the feature for context (best-effort; never block on this)
+ let context: { headline?: string; biz?: string } = {};
+ if (b.feature_id) {
+ try {
+ const c = await query(
+ `SELECT mf.headline, b.name AS biz FROM magazine_features mf JOIN businesses b ON b.id = mf.business_id WHERE mf.id = $1`,
+ [b.feature_id]
+ );
+ if (c.rowCount) context = c.rows[0];
+ } catch {}
+ }
+ const escEm = (s: any) => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]!));
+ const html = `<div style="font-family:Georgia,serif;max-width:560px;color:#1a1a1a;padding:24px;background:#faf6ee">
+<h2 style="font-family:Georgia,serif;font-style:italic;font-size:26px;color:#6a3a1a;margin:0 0 8px">Reader ${escEm(kind)}</h2>
+<div style="font-size:11px;letter-spacing:.3em;text-transform:uppercase;color:#8a6d3b;margin-bottom:20px">The Corridor · feedback #${fbId} · ${new Date().toLocaleString('en-US',{dateStyle:'long',timeStyle:'short'})}</div>
+<table style="border-collapse:collapse;font-size:14px;margin-bottom:20px">
+${context.headline ? `<tr><td style="padding:6px 14px 6px 0;color:#888;width:120px">Feature</td><td><a href="http://127.0.0.1:9780/magazine/${escEm(b.feature_id)}">${escEm(context.headline)}</a></td></tr>` : ''}
+${context.biz ? `<tr><td style="padding:6px 14px 6px 0;color:#888">Business</td><td>${escEm(context.biz)}</td></tr>` : ''}
+${b.contact ? `<tr><td style="padding:6px 14px 6px 0;color:#888">Reader</td><td><a href="mailto:${escEm(b.contact)}">${escEm(b.contact)}</a></td></tr>` : '<tr><td style="padding:6px 14px 6px 0;color:#888">Reader</td><td><i>anonymous</i></td></tr>'}
+${b.source_path ? `<tr><td style="padding:6px 14px 6px 0;color:#888">From</td><td><code style="font-size:12px;color:#6e6356">${escEm(b.source_path)}</code></td></tr>` : ''}
+</table>
+<div style="padding:14px 18px;background:rgba(184,153,104,0.1);border-left:3px solid #8a6d3b;font-style:italic;font-size:15px;line-height:1.5">${escEm(b.body)}</div>
+<div style="margin-top:24px;font-size:11px;color:#888">Triage at <a href="http://127.0.0.1:9780/feedback.html">/feedback.html</a> · mark reviewed when handled.</div>
+</div>`;
+ try {
+ const georgeAuth = 'Basic ' + Buffer.from('admin:DWSecure2024!').toString('base64');
+ await fetch('http://localhost:9850/api/send', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', Authorization: georgeAuth },
+ body: JSON.stringify({
+ to: 'steve@designerwallcoverings.com',
+ subject: `🪶 Corridor reader ${kind}${context.headline ? ' · ' + context.headline.slice(0, 60) : ''}`,
+ body: html,
+ isHtml: true,
+ account: 'info'
+ })
+ });
+ } catch (e) { /* never block feedback intake on email send */ }
+ }
+
+ res.json({ ok: true, id: fbId });
} catch (e: any) {
res.status(500).json({ error: e.message });
}
← 6ffa8b0 iter 141: category emoji map on /scoreboard — 36 verticals m
·
back to Ventura Corridor
·
iter 144: cover-image upload — multer endpoint + /covers sta 4c4257c →