Admin > Reports: counters audit (views vs delivery logs, login days charged vs shown, featured views, clicks vs views, spend vs budget), daily with Telegram alert

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-15 05:32:13 -05:00
parent 86a7f83874
commit 84a9063bbe
4 changed files with 90 additions and 2 deletions
+8 -1
View File
@@ -32,7 +32,8 @@ const blog = require('./blog');
const adminMember = require('./adminmember');
const syndicate = require('./syndicate');
const releases = require('./releases');
const updates = require('./updates'); // member update emails from Admin > Releases (Marty, 2026-09-14)
const updates = require('./updates');
const audit = require('./audit'); // counter audit: views vs delivery logs, charges vs shows (Marty, 2026-09-15) // member update emails from Admin > Releases (Marty, 2026-09-14)
const leaderboard = require('./leaderboard');
const toolkit = require('./toolkit');
const videomaker = require('./videomaker'); // Circuit tool: promo videos with the member's own end card (ffmpeg in the image) // badge-gated promo toolkit + AI Copy Engine (Surge and up) (Marty, 2026-09-14) // referral contest: /leaderboard, Overview card, weekly + monthly winners (Marty, 2026-09-14) // release notes + roadmap: /whats-new, Overview card, Admin > Releases (Marty, 2026-09-14) // blog -> Blotato -> X + Instagram on publish (Marty, 2026-09-13) // admin member card: search, drilldown, edits (Marty, 2026-09-13) // admin-written coaching articles, server-rendered public /blog with SEO metadata (Marty, 2026-09-12)
@@ -379,6 +380,8 @@ async function boot() {
releases.init({ dataDir: DATA_DIR });
toolkit.init({ dataDir: DATA_DIR, ads, accounts, siteConfig, coach, messages, promos, videomaker, chain });
updates.init({ dataDir: DATA_DIR, accounts, releases, mailer, drip, sendy, adminEmail: ADMIN_EMAIL });
audit.init({ notify: text => { const sc = siteConfig(); if (sc.telegramBotToken && sc.telegramAdminChatId) telegramSend(sc.telegramAdminChatId, text).catch(() => {}); else if (ADMIN_EMAIL && mailer.hasKey()) mailer.send(ADMIN_EMAIL, 'InstantAdPay: counter audit', text).catch(() => {}); } });
setTimeout(() => audit.dailyTick(), 5 * 60 * 1000); setInterval(() => audit.dailyTick(), 24 * 60 * 60 * 1000);
videomaker.init({ dataDir: DATA_DIR, spaces, accounts });
leaderboard.init({ chain, accounts, ads, dataDir: DATA_DIR, siteConfig, pushFeed, adminEmail: ADMIN_EMAIL,
notify: async text => { const sc = siteConfig(); if (!sc.telegramBotToken || !sc.telegramEchoChatId) return; await telegramSend(sc.telegramEchoChatId, text, sc.telegramEchoTopicId); if (String(sc.leaderboardAnnounceGeneral || '1') !== '0') await telegramSend(sc.telegramEchoChatId, text, null); } });
@@ -2418,6 +2421,10 @@ const server = http.createServer(async (req, res) => {
const r = await ads.adminSetStatus(m[1], m[2] === 'pause' ? 'paused' : 'active');
return json(res, r.error ? 400 : 200, r);
}
if (p === '/api/admin/audit' && req.method === 'GET') { // counters reconciled against delivery logs
if (!isAdmin(req)) return json(res, 401, { error: 'auth' });
return json(res, 200, await audit.run());
}
if (p === '/api/admin/reports' && req.method === 'GET') {
if (!isAdmin(req)) return json(res, 401, { error: 'auth' });
return json(res, 200, { reports: await reports.list(200) });