Traffic sources: referring domain on join-link visits and joins (per-member table), click sources per campaign (surface or outside host)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-10 10:12:13 -05:00
parent 6e187c4f41
commit ecdee89be1
6 changed files with 74 additions and 22 deletions
+9 -9
View File
@@ -33,7 +33,7 @@ function newCode(taken) {
return c;
}
const pub = a => a ? { email: a.email, sponsorRef: a.sponsorRef || '', code: a.code || null,
username: a.username || null, memberId: a.memberId || 0, joinedVia: a.joinedVia || null,
username: a.username || null, memberId: a.memberId || 0, joinedVia: a.joinedVia || null, joinedRef: a.joinedRef || null,
lineBannerUrl: a.lineBannerUrl || null, lineTargetUrl: a.lineTargetUrl || null, wallOffers: a.wallOffers || null,
avatarUrl: a.avatarUrl || null, bio: a.bio || null, socials: a.socials || null,
chatAvailable: a.chatAvailable === false ? false : true, lastSeen: a.lastSeen || 0,
@@ -75,11 +75,11 @@ const J = {
if (!a || !a.pass || !checkPassword(password, a.pass)) return { error: 'Wrong email or password.' };
return { ok: true, account: pub(a) };
},
async ensure(e, ref, via) {
async ensure(e, ref, via, joinedRef) {
let created = false;
if (!this.db.byEmail[e]) {
const code = newCode(c => this.db.byCode[c]);
this.db.byEmail[e] = { email: e, pass: null, sponsorRef: ref, code, address: null, created: Date.now(), joinedVia: via || null };
this.db.byEmail[e] = { email: e, pass: null, sponsorRef: ref, code, address: null, created: Date.now(), joinedVia: via || null, joinedRef: joinedRef || null };
this.db.byCode[code] = e;
created = true;
this.save();
@@ -204,7 +204,7 @@ const J = {
// ---- MySQL mode ----
const rowPub = r => r ? pub({ email: r.email, sponsorRef: r.sponsor_ref, code: r.code,
username: r.username, memberId: r.member_id || 0, joinedVia: r.joined_via || null,
username: r.username, memberId: r.member_id || 0, joinedVia: r.joined_via || null, joinedRef: r.joined_ref || null,
lineBannerUrl: r.line_banner_url, lineTargetUrl: r.line_target_url, wallOffers: r.wall_offers || null,
avatarUrl: r.avatar_url, bio: r.bio, socials: r.socials,
chatAvailable: r.chat_available === 0 ? false : true, lastSeen: Number(r.last_seen || 0),
@@ -228,12 +228,12 @@ const D = {
if (!rows.length || !rows[0].pass || !checkPassword(password, rows[0].pass)) return { error: 'Wrong email or password.' };
return { ok: true, account: rowPub(rows[0]) };
},
async ensure(e, ref, via) {
async ensure(e, ref, via, joinedRef) {
const code = newCode();
let created = false;
try {
await db.q('INSERT INTO accounts (email,pass,sponsor_ref,code,address,created,joined_via) VALUES (?,NULL,?,?,NULL,?,?)',
[e, ref, code, Date.now(), via || null]);
await db.q('INSERT INTO accounts (email,pass,sponsor_ref,code,address,created,joined_via,joined_ref) VALUES (?,NULL,?,?,NULL,?,?,?)',
[e, ref, code, Date.now(), via || null, joinedRef || null]);
created = true;
} catch (err) {
if (err.code !== 'ER_DUP_ENTRY') throw err;
@@ -360,10 +360,10 @@ async function signup(email, password, sponsorRef) {
return impl().signup(e, String(password), String(sponsorRef || ''));
}
async function login(email, password) { return impl().login(normEmail(email), String(password || '')); }
async function ensure(email, sponsorRef, via) {
async function ensure(email, sponsorRef, via, joinedRef) {
const e = normEmail(email);
if (!EMAIL_RE.test(e)) return { error: 'That email address does not look right.' };
return impl().ensure(e, String(sponsorRef || ''), String(via || '').toLowerCase().slice(0, 20) || null);
return impl().ensure(e, String(sponsorRef || ''), String(via || '').toLowerCase().slice(0, 20) || null, String(joinedRef || '').toLowerCase().slice(0, 80) || null);
}
async function byEmail(email) { return impl().byEmail(normEmail(email)); }
async function byAddress(address) { return impl().byAddress(normAddr(address)); }