Ad reporting + featured rotates one link + clear campaign form on submit

- Ad reporting (auto-approved ads need a safety valve): new reports.js store
  (dual-mode) + ad_reports table + POST /api/report-ad; a "⚠ report" control on
  served ad slots opens a reason prompt and notifies the admin by email
  (siteConfig.adminEmail / ADMIN_EMAIL).
- Featured links now show ONE link at a time and cycle (true rotation), instead
  of listing all links at once.
- Campaign form now clears EVERY field on submit (target/creative/title/etc.).
  A leftover target URL was carrying into the next campaign — the same reason a
  video short was saved with the wrong (massifly) CTA target.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-07 07:57:12 -05:00
parent ea1b2ea0ac
commit 4991fe96e8
11 changed files with 133 additions and 18 deletions
+20
View File
@@ -17,6 +17,7 @@ const accounts = require('./accounts');
const ads = require('./ads');
const mailer = require('./mailer');
const messages = require('./messages');
const reports = require('./reports');
const spaces = require('./spaces'); // DO Spaces video storage (inert unless DO_SPACES_* set)
let QR = null; try { QR = require('qrcode'); } catch (e) { /* optional */ }
const chatbot = require('./chatbot');
@@ -126,6 +127,7 @@ async function boot() {
ads.init({ dataDir: DATA_DIR, chain });
mailer.init({ dataDir: DATA_DIR });
messages.init({ dataDir: DATA_DIR });
reports.init({ dataDir: DATA_DIR });
chatbot.init({ dataDir: DATA_DIR, chain });
setTimeout(() => ads.dailySweep().catch(e => console.error('sweep', e.message)), 60 * 1000);
setInterval(() => ads.dailySweep().catch(e => console.error('sweep', e.message)), 60 * 60 * 1000);
@@ -705,6 +707,24 @@ const server = http.createServer(async (req, res) => {
return json(res, 200, { ok: true, balanceWei: BigInt(nb || '0x0').toString() });
} catch (e) { return json(res, 502, { error: 'Faucet is unavailable right now.' }); }
}
// -- report an ad (auto-approved ads need a safety valve): store + notify admin
if (p === '/api/report-ad' && req.method === 'POST') {
const b = await readBody(req);
if (!Number(b.campaignId)) return json(res, 400, { error: 'Which ad?' });
const s = await auth.fromRequest(req);
const who = (s && s.email) || '';
const rec = await reports.add(b.campaignId, who, b.reason, b.note);
try {
const adminEmail = siteConfig().adminEmail || process.env.ADMIN_EMAIL || '';
if (adminEmail && mailer.hasKey()) {
mailer.send(adminEmail, 'Ad reported on InstantAdPay (campaign #' + rec.campaignId + ')',
'A member flagged an ad.\n\nCampaign: #' + rec.campaignId + '\nReason: ' + rec.reason
+ '\nReported by: ' + (who || 'anonymous') + '\nNote: ' + (String(b.note || '').slice(0, 500) || '(none)')
+ '\n\nPause or review it from the admin.').catch(() => {});
}
} catch (e) {}
return json(res, 200, { ok: true });
}
if (p === '/api/my/chat/send' && req.method === 'POST') {
const s = await auth.fromRequest(req);
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });