From c9f7a50b3074b2ec914b14928926884865005e2c Mon Sep 17 00:00:00 2001 From: martbost Date: Fri, 11 Sep 2026 09:06:13 -0500 Subject: [PATCH] Admin Members: show the sponsor's username, note share-code joins, flag dead sponsor links Co-Authored-By: Claude Fable 5.1 --- public/admin.html | 2 +- public/assets/admin.js | 2 +- server.js | 9 +++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/public/admin.html b/public/admin.html index 5bbf109..297e3e2 100644 --- a/public/admin.html +++ b/public/admin.html @@ -316,6 +316,6 @@ - + diff --git a/public/assets/admin.js b/public/assets/admin.js index 37f7f93..b1cf68a 100644 --- a/public/assets/admin.js +++ b/public/assets/admin.js @@ -289,7 +289,7 @@ + list.map(a => '' + esc(a.email) + '' + (a.username ? '@' + esc(a.username) : 'none') + '' + '' + (a.memberId ? '#' + a.memberId : 'free') + '' + '' + (a.address ? esc(a.address.slice(0, 8) + '…' + a.address.slice(-6)) : 'none') + '' - + '' + esc(a.sponsorRef || '') + '' + (a.positions ? a.positions : '0') + '' + esc(a.joinedVia || '') + '' + esc(a.code || '') + '' + + '' + (a.sponsorName ? esc(a.sponsorName) + (a.sponsorVia === 'code' ? ' via code ' + esc(a.sponsorRef) + '' : a.sponsorVia === 'member #' ? ' via #' + esc(a.sponsorRef) + '' : '') : a.sponsorRef ? 'dead link: ' + esc(a.sponsorRef) + '' : 'none') + '' + (a.positions ? a.positions : '0') + '' + esc(a.joinedVia || '') + '' + esc(a.code || '') + '' + '' + when(a.created) + '' + '').join(''); } diff --git a/server.js b/server.js index e8f8c8b..984476c 100644 --- a/server.js +++ b/server.js @@ -1779,6 +1779,15 @@ const server = http.createServer(async (req, res) => { if (p === '/api/admin/members' && req.method === 'GET') { if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); const members = await accounts.listAll(500); + // resolve each sponsor token (username, share code or member #) to the sponsor's name + const byTok = {}; + for (const m of members) for (const t of [m.username, m.code, m.memberId ? String(m.memberId) : null]) if (t) byTok[String(t).toLowerCase()] = m; + for (const m of members) { + const t = String(m.sponsorRef || '').toLowerCase(); + const sp = t ? byTok[t] : null; + m.sponsorName = sp ? (sp.username ? '@' + sp.username : (sp.memberId ? 'member #' + sp.memberId : sp.email)) : null; + m.sponsorVia = sp ? (t === String(sp.username || '').toLowerCase() ? 'username' : t === String(sp.code || '').toLowerCase() ? 'code' : 'member #') : (t ? 'unresolved' : ''); + } for (const m of members) { try { const ps = await accounts.positions(m.email); m.positions = ps.length; m.positionIds = ps.map(p => p.memberId).filter(Boolean); } catch (e) { m.positions = 0; } } return json(res, 200, { members }); }