Free members refer from day one: share codes with late chain binding
Every account gets a share code at signup; /join/<code> attributes first-touch site-side and resolves to the referrer's CURRENT on-chain id at the referral's buy time, so activating any time before your people buy locks the line to you. Joining through a code emails the referrer an activate-payouts nudge. Buy flow re-resolves the sponsor at click time. Copy updated across home and members; assets bumped to v=20260904g. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+32
-8
@@ -16,6 +16,18 @@ function load() {
|
||||
try { db = JSON.parse(fs.readFileSync(FILE(), 'utf8')); } catch (e) {}
|
||||
if (!db || !db.v) db = { v: 2, byEmail: {}, byAddress: {}, joins: 0 };
|
||||
if (db.v === 1) { db.v = 2; db.byEmail = db.byEmail || {}; } // early rehearsal file
|
||||
if (!db.byCode) db.byCode = {};
|
||||
// every account carries a share code from day one (backfill older records)
|
||||
for (const a of Object.values(db.byEmail)) {
|
||||
if (!a.code) { a.code = genCode(); db.byCode[a.code] = a.email; }
|
||||
else if (!db.byCode[a.code]) db.byCode[a.code] = a.email;
|
||||
}
|
||||
}
|
||||
function genCode() {
|
||||
let c;
|
||||
do { c = crypto.randomBytes(5).toString('base64url').replace(/[-_]/g, '').slice(0, 7).toLowerCase(); }
|
||||
while (!c || c.length < 6 || (db.byCode && db.byCode[c]) || /^\d+$/.test(c));
|
||||
return c;
|
||||
}
|
||||
function save() {
|
||||
try {
|
||||
@@ -45,21 +57,24 @@ const normEmail = e => String(e || '').trim().toLowerCase();
|
||||
const normAddr = a => String(a || '').trim().toLowerCase();
|
||||
|
||||
// ---- email accounts (the normal join path) ----
|
||||
function signup(email, password, sponsorId) {
|
||||
function signup(email, password, sponsorRef) {
|
||||
const e = normEmail(email);
|
||||
if (!EMAIL_RE.test(e)) return { error: 'That email address does not look right.' };
|
||||
if (String(password || '').length < 8) return { error: 'Password needs at least 8 characters.' };
|
||||
if (db.byEmail[e]) return { error: 'That email already has an account. Log in instead.' };
|
||||
const code = genCode();
|
||||
db.byEmail[e] = {
|
||||
email: e,
|
||||
pass: hashPassword(password),
|
||||
sponsorId: Number(sponsorId) || 0, // first touch, written on-chain at first purchase
|
||||
sponsorRef: String(sponsorRef || ''), // first touch; resolved to a chain id at buy time
|
||||
code,
|
||||
address: null,
|
||||
created: Date.now()
|
||||
};
|
||||
db.byCode[code] = e;
|
||||
db.joins += 1;
|
||||
save();
|
||||
return { ok: true, account: publicView(db.byEmail[e]) };
|
||||
return { ok: true, created: true, account: publicView(db.byEmail[e]) };
|
||||
}
|
||||
function login(email, password) {
|
||||
const e = normEmail(email);
|
||||
@@ -70,15 +85,23 @@ function login(email, password) {
|
||||
}
|
||||
// Passwordless path: a verified email code proves ownership, so the account
|
||||
// may exist with no password at all.
|
||||
function ensure(email, sponsorId) {
|
||||
function ensure(email, sponsorRef) {
|
||||
const e = normEmail(email);
|
||||
if (!EMAIL_RE.test(e)) return { error: 'That email address does not look right.' };
|
||||
let created = false;
|
||||
if (!db.byEmail[e]) {
|
||||
db.byEmail[e] = { email: e, pass: null, sponsorId: Number(sponsorId) || 0, address: null, created: Date.now() };
|
||||
const code = genCode();
|
||||
db.byEmail[e] = { email: e, pass: null, sponsorRef: String(sponsorRef || ''), code, address: null, created: Date.now() };
|
||||
db.byCode[code] = e;
|
||||
db.joins += 1;
|
||||
created = true;
|
||||
save();
|
||||
}
|
||||
return { ok: true, account: publicView(db.byEmail[e]) };
|
||||
return { ok: true, created, account: publicView(db.byEmail[e]) };
|
||||
}
|
||||
function byCode(code) {
|
||||
const e = db.byCode[String(code || '').toLowerCase()];
|
||||
return e ? publicView(db.byEmail[e]) : null;
|
||||
}
|
||||
function byEmail(email) { const a = db.byEmail[normEmail(email)]; return a ? publicView(a) : null; }
|
||||
function byAddress(address) {
|
||||
@@ -104,8 +127,9 @@ function linkWallet(email, address) {
|
||||
}
|
||||
|
||||
function publicView(a) {
|
||||
return { email: a.email, sponsorId: a.sponsorId || 0, address: a.address || null, created: a.created };
|
||||
return { email: a.email, sponsorRef: a.sponsorRef || String(a.sponsorId || '') || '',
|
||||
code: a.code || null, address: a.address || null, created: a.created };
|
||||
}
|
||||
function count() { return Object.keys(db.byEmail).length; }
|
||||
|
||||
module.exports = { init, signup, login, ensure, byEmail, byAddress, linkWallet, count };
|
||||
module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, linkWallet, count };
|
||||
|
||||
Reference in New Issue
Block a user