Add public member dashboard at /my/:id
Members enter their on-chain ID (or follow a /my/46-style link) and see their own position: tier/level/directs/earnings facts, their subtree pyramid with downline rollups and open slots, classified payment history, and lineage to root — all chain-derived data only, none of the admin operational config. Public endpoint /api/public/member is cached (120s) and rate-limited (20/min/IP). Linked from the public footers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -400,4 +400,53 @@ function getMatrixTree() {
|
||||
return { ready: true, snapshotAt: state.snapshotAt, memberCount: Object.keys(state.members).length, root, unplaced: unplaced.length ? unplaced : undefined };
|
||||
}
|
||||
|
||||
module.exports = { startIndexer, getPayoutsPublic, verifyMember, memberLookup, getMatrixTree, isInTeam, CONTRACT };
|
||||
// subtree rooted at `id` from the snapshot: nodes to `showDepth`, rollups from the FULL subtree
|
||||
function getSubtree(id, showDepth = 3) {
|
||||
if (!state || !state.members[id]) return null;
|
||||
const seen = new Set();
|
||||
function node(nid, depth) {
|
||||
if (!nid || seen.has(nid)) return null;
|
||||
seen.add(nid);
|
||||
const m = state.members[nid];
|
||||
if (!m) return null;
|
||||
const l = node(m.l, depth + 1), r = node(m.r, depth + 1);
|
||||
const n = {
|
||||
id: nid, tier: m.tier, level: m.level, levelName: levelName(m.level || 1),
|
||||
directCount: m.directCount || 0, earnedPol: m.earnedPol || 0,
|
||||
downCount: 0, downPol: 0
|
||||
};
|
||||
for (const c of [l, r]) if (c) { n.downCount += 1 + c.downCount; n.downPol = +(n.downPol + c.earnedPol + c.downPol).toFixed(2); }
|
||||
if (depth < showDepth) { n.left = l; n.right = r; }
|
||||
return n;
|
||||
}
|
||||
return node(id, 0);
|
||||
}
|
||||
|
||||
// everything a member may see about their own position — chain data only
|
||||
async function memberPublic(id) {
|
||||
const m = await fetchMember(id);
|
||||
if (!m) return { registered: false, id };
|
||||
await fetchCosts().catch(() => {});
|
||||
const out = {
|
||||
registered: true, id, account: m.account, joinedAt: m.joinedAt,
|
||||
tier: m.tier, tierName: tierName(m.tier), level: m.level, levelName: levelName(m.level),
|
||||
directCount: m.directCount, totalEarnedPol: m.totalEarnedPol, totalPaidPol: m.totalPaidPol,
|
||||
referrerId: m.referrerId, uplineId: m.uplineId
|
||||
};
|
||||
try {
|
||||
const inc = await fetchIncome(id);
|
||||
out.income = inc.map(p => ({ fromId: p.fromId, pol: p.pol, ts: p.ts, desc: describeIncome(p.fromTier, p.level, p.pol) })).reverse().slice(0, 50);
|
||||
} catch (e) { out.income = []; }
|
||||
out.subtree = getSubtree(id, 3);
|
||||
const chain = [];
|
||||
const seen = new Set([id]);
|
||||
let cur = m.uplineId;
|
||||
for (let i = 0; i < 40 && cur && !seen.has(cur); i++) {
|
||||
seen.add(cur); chain.push(cur);
|
||||
cur = state && state.members[cur] ? state.members[cur].uplineId : 0;
|
||||
}
|
||||
out.uplineChain = chain;
|
||||
return out;
|
||||
}
|
||||
|
||||
module.exports = { startIndexer, getPayoutsPublic, verifyMember, memberLookup, memberPublic, getMatrixTree, isInTeam, CONTRACT };
|
||||
|
||||
Reference in New Issue
Block a user