Admin portal (/admin) + free house ads; POL amounts show two decimals
- /admin: email magic-code sign-in allowlisted to ADMIN_EMAIL, 12h admin session (cookie iap.adm, persisted in the volume). Bearer ADMIN_PASSWORD API access still works. Member area shows an Admin link for that email. - House ads: admin places banner/text/login/solo/video/featured/visits campaigns owned by house@instantadpay.com that cost nothing; budget is only a delivery cap, spend is never charged or burned. - Admin APIs: overview, all campaigns (+pause/resume any), members (+re-point sponsor), reports (+resolve), pending burns, rates/site config get+patch, creative upload. - fmtPol rounds to two decimals everywhere (dashboard, toasts, prices). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+18
-1
@@ -159,6 +159,13 @@ const J = {
|
||||
this.save();
|
||||
return { ok: true, account: pub(acct) };
|
||||
},
|
||||
async listAll(limit) { return Object.values(this.db.byEmail).sort((a, b) => (b.created || 0) - (a.created || 0)).slice(0, limit).map(pub); },
|
||||
async setSponsorRef(e, ref) {
|
||||
const acct = this.db.byEmail[e];
|
||||
if (!acct) return { error: 'No such account.' };
|
||||
acct.sponsorRef = ref; this.save();
|
||||
return { ok: true, account: pub(acct) };
|
||||
},
|
||||
async count() { return Object.keys(this.db.byEmail).length; }
|
||||
};
|
||||
|
||||
@@ -268,6 +275,12 @@ const D = {
|
||||
}
|
||||
return { ok: true, account: await this.byEmail(e) };
|
||||
},
|
||||
async listAll(limit) { const rows = await db.q('SELECT * FROM accounts ORDER BY created DESC LIMIT ?', [Number(limit) || 500]); return rows.map(rowPub); },
|
||||
async setSponsorRef(e, ref) {
|
||||
const r = await db.q('UPDATE accounts SET sponsor_ref=? WHERE email=?', [ref, e]);
|
||||
if (!r.affectedRows) return { error: 'No such account.' };
|
||||
return { ok: true, account: await this.byEmail(e) };
|
||||
},
|
||||
async count() { const r = await db.q('SELECT COUNT(*) n FROM accounts'); return Number(r[0].n); }
|
||||
};
|
||||
|
||||
@@ -332,6 +345,10 @@ async function linkWallet(email, address) {
|
||||
return impl().linkWallet(normEmail(email), a);
|
||||
}
|
||||
async function count() { return impl().count(); }
|
||||
// admin: newest-first account list, and re-pointing a member's sponsor (a
|
||||
// username, share code, or numeric member id: the same tokens join links use)
|
||||
async function listAll(limit = 500) { return impl().listAll(limit); }
|
||||
async function setSponsorRef(email, ref) { return impl().setSponsorRef(normEmail(email), String(ref || '').trim().toLowerCase().slice(0, 40)); }
|
||||
|
||||
// resolve a member's DIRECT sponsor account (the token they joined under)
|
||||
async function sponsorOf(email) {
|
||||
@@ -355,7 +372,7 @@ async function getChatSettings(email) {
|
||||
return { available: a ? a.chatAvailable !== false : true, mutes: await impl().getMutes(String(email || '').toLowerCase()) };
|
||||
}
|
||||
|
||||
module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, byUsername,
|
||||
module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, byUsername, listAll, setSponsorRef,
|
||||
setUsername, setMemberId, namesForMembers, listByReferrer, downline, linkWallet, count,
|
||||
setLineBanner: (e, b, t) => impl().setLineBanner(String(e || '').toLowerCase(), b, t),
|
||||
setProfile: (e, a, bio, socials) => impl().setProfile(String(e || '').toLowerCase(), a, bio, socials),
|
||||
|
||||
Reference in New Issue
Block a user