diff --git a/coach.js b/coach.js index f8aedde..7cf0170 100644 --- a/coach.js +++ b/coach.js @@ -104,7 +104,8 @@ const J = { async addView(token, angle, ts, ref) { this.db.views.push({ token, angle, ts, ref: ref || '' }); if (this.db.views.length > 50000) this.db.views = this.db.views.slice(-40000); this.save(); }, async addClick(campaignId, src, ts) { this.db.clicks = this.db.clicks || []; this.db.clicks.push({ campaignId, src, ts }); if (this.db.clicks.length > 50000) this.db.clicks = this.db.clicks.slice(-40000); this.save(); }, async clicksFor(ids) { return (this.db.clicks || []).filter(c => ids.includes(c.campaignId)); }, - async views(tokens, since) { return this.db.views.filter(v => tokens.includes(v.token) && v.ts >= since); } + async views(tokens, since) { return this.db.views.filter(v => tokens.includes(v.token) && v.ts >= since); }, + async viewsSince(since) { return this.db.views.filter(v => v.ts >= since); } }; const D = { async lastNudge(email) { const r = await db.q('SELECT rung, ts FROM nudges WHERE email=?', [email]); return r[0] ? { rung: r[0].rung, ts: Number(r[0].ts) } : null; }, @@ -131,6 +132,10 @@ const D = { if (!tokens.length) return []; const rows = await db.q('SELECT token, angle, ts, ref FROM join_views WHERE ts>=? AND token IN (' + tokens.map(() => '?').join(',') + ')', [since, ...tokens]); return rows.map(r => ({ token: r.token, angle: r.angle, ts: Number(r.ts), ref: r.ref || '' })); + }, + async viewsSince(since) { + const rows = await db.q('SELECT token, angle, ts, ref FROM join_views WHERE ts>=?', [since]); + return rows.map(r => ({ token: r.token, angle: r.angle, ts: Number(r.ts), ref: r.ref || '' })); } }; const impl = () => db.enabled() ? D : J; @@ -249,4 +254,5 @@ function init(opts) { DATA_DIR = opts.dataDir; chain = opts.chain; accounts = opts.accounts; mailer = opts.mailer; J.load(); } -module.exports = { init, RUNGS, rungFor, describe, coachView, prospects, saveProspect, removeProspect, STATUSES, recordView, linkStats, nudgeTick, recordClick, clickSources, refHost }; +async function viewsSince(since) { return impl().viewsSince(since); } +module.exports = { init, RUNGS, rungFor, describe, coachView, prospects, saveProspect, removeProspect, STATUSES, recordView, linkStats, nudgeTick, recordClick, clickSources, refHost, viewsSince }; diff --git a/db.js b/db.js index 0001f9d..f8ca8b1 100644 --- a/db.js +++ b/db.js @@ -144,6 +144,10 @@ async function bootstrap() { ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); await q(`CREATE TABLE IF NOT EXISTS nudges (email VARCHAR(190) PRIMARY KEY, rung INT NOT NULL, ts BIGINT NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); await q(`CREATE TABLE IF NOT EXISTS digests (email VARCHAR(190) PRIMARY KEY, ts BIGINT NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); + await q(`CREATE TABLE IF NOT EXISTS page_hits ( + day CHAR(10) NOT NULL, host VARCHAR(80) NOT NULL, path VARCHAR(40) NOT NULL, n INT NOT NULL DEFAULT 0, + PRIMARY KEY (day, host, path) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); // admin Traffic tab: public page views by referring domain await q(`CREATE TABLE IF NOT EXISTS adoptions ( id INT AUTO_INCREMENT PRIMARY KEY, adoptee VARCHAR(190) NOT NULL, adopter VARCHAR(190) NOT NULL, diff --git a/public/admin.html b/public/admin.html index 297e3e2..0204a9c 100644 --- a/public/admin.html +++ b/public/admin.html @@ -90,6 +90,7 @@ + @@ -251,6 +252,19 @@ +