From e58de012092e550823d8a5a02d47cddf6bc1146a Mon Sep 17 00:00:00 2001 From: martbost Date: Tue, 8 Sep 2026 15:28:28 -0500 Subject: [PATCH] Fallback unattributed joins under #1 instead of root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Members were activating with sponsorId 0 (their own root) whenever their sponsor couldn't be resolved — dead pre-wipe links (dkain) or no link at all — so their purchases rolled to admin instead of building the tree. Default an unresolvable sponsor to the configured catch position (siteConfig.defaultSponsorId, default 1) in both the buy path (/api/sponsor) and the free-activation path (/api/me), guarded so #1 itself is never self-sponsored. Co-Authored-By: Claude Opus 4.8 --- server.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index 2593535..b06ecb9 100644 --- a/server.js +++ b/server.js @@ -428,7 +428,10 @@ const server = http.createServer(async (req, res) => { const s = await auth.fromRequest(req); const acct = s && s.email ? await accounts.byEmail(s.email) : null; const tok = (acct && acct.sponsorRef) || parseCookies(req)['iap.sponsor'] || ''; - const sponsorId = await resolveSponsorToken(tok); + let sponsorId = await resolveSponsorToken(tok); + // orphan fallback: an unresolvable/absent sponsor (dead link, no link) lands + // the new member under the configured catch position (#1) instead of root + if (!sponsorId && (!acct || acct.memberId !== (Number(siteConfig().defaultSponsorId) || 1))) sponsorId = Number(siteConfig().defaultSponsorId) || 1; let name = null, avatarUrl = null; if (tok) { const t = tok.toLowerCase(); @@ -552,7 +555,9 @@ const server = http.createServer(async (req, res) => { if (!s) return json(res, 200, { signedIn: false }); const memberId = await auth.refreshMemberId(s); const acct = (s.email && await accounts.byEmail(s.email)) || (s.address && await accounts.byAddress(s.address)) || null; - const sponsorId = await resolveSponsorToken((acct && acct.sponsorRef) || parseCookies(req)['iap.sponsor']); + let sponsorId = await resolveSponsorToken((acct && acct.sponsorRef) || parseCookies(req)['iap.sponsor']); + const _defSpon = Number(siteConfig().defaultSponsorId) || 1; + if (!sponsorId && memberId !== _defSpon) sponsorId = _defSpon; // orphan fallback → #1 (never self-sponsor) if (memberId && acct && acct.memberId !== memberId) accounts.setMemberId(acct.email, memberId).catch(() => {}); const out = { signedIn: true, email: s.email || (acct && acct.email) || null, address: s.address || (acct && acct.address) || null, memberId,