LinkSpin test area: InstantAdPay engine fork rebranded, network registry, sponsor carry-over with engine activation and claim window, rotator with /r/ redirects, link-domain mini-sites
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+55
@@ -0,0 +1,55 @@
|
||||
// Network registry (LinkSpin, 2026-09-15): who is who across the Crypto Team Build Network
|
||||
// properties, keyed by email (and by main wallet once one exists). InstantAdPay writes it as
|
||||
// members join, name themselves, link a wallet and bind a sponsor; LinkSpin reads it at sign-in
|
||||
// and wallet link so a member's sponsor line carries over. Nothing here moves money: each
|
||||
// contract still binds its own sponsor at first activation. Dual-mode store like the rest of
|
||||
// the engine (MySQL when NETWORK_DB_URL is set, JSON on the volume otherwise). In the test area
|
||||
// the JSON file is seeded from an export of the live InstantAdPay accounts.
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let DATA_DIR = null;
|
||||
const norm = e => String(e || '').trim().toLowerCase();
|
||||
|
||||
const J = {
|
||||
db: null,
|
||||
FILE: () => path.join(DATA_DIR, 'registry.json'),
|
||||
load() { try { this.db = JSON.parse(fs.readFileSync(this.FILE(), 'utf8')); } catch (e) { this.db = { v: 1, byEmail: {} }; } if (!this.db.byEmail) this.db.byEmail = {}; },
|
||||
save() { try { fs.writeFileSync(this.FILE(), JSON.stringify(this.db)); } catch (e) {} },
|
||||
async get(email) { if (!this.db) this.load(); return this.db.byEmail[norm(email)] || null; },
|
||||
async byWallet(addr) { if (!this.db) this.load(); const a = norm(addr); return Object.values(this.db.byEmail).find(r => norm(r.wallet) === a) || null; },
|
||||
async byUsername(u) { if (!this.db) this.load(); const x = norm(u); return Object.values(this.db.byEmail).find(r => norm(r.username) === x) || null; },
|
||||
async upsert(rec) {
|
||||
if (!this.db) this.load();
|
||||
const e = norm(rec.email); if (!e) return null;
|
||||
const cur = this.db.byEmail[e] || { email: e, created: Date.now() };
|
||||
for (const k of ['username', 'wallet', 'sponsorEmail', 'firstProperty', 'joinedAt']) if (rec[k] != null && rec[k] !== '') cur[k] = k === 'wallet' || k === 'sponsorEmail' ? norm(rec[k]) : rec[k];
|
||||
cur.updated = Date.now(); this.db.byEmail[e] = cur; this.save(); return cur;
|
||||
},
|
||||
async all() { if (!this.db) this.load(); return Object.values(this.db.byEmail); },
|
||||
async count() { if (!this.db) this.load(); return Object.keys(this.db.byEmail).length; }
|
||||
};
|
||||
|
||||
function init(opts) { DATA_DIR = opts.dataDir; }
|
||||
async function get(email) { return J.get(email); }
|
||||
async function byWallet(addr) { return J.byWallet(addr); }
|
||||
async function byUsername(u) { return J.byUsername(u); }
|
||||
async function upsert(rec) { return J.upsert(rec); }
|
||||
async function all() { return J.all(); }
|
||||
async function count() { return J.count(); }
|
||||
// the sponsor chain above an email, nearest first, bounded (a loop in bad data must not hang a request)
|
||||
async function chainUp(email, max = 25) {
|
||||
const out = []; let cur = await get(email); const seen = new Set([norm(email)]);
|
||||
while (cur && cur.sponsorEmail && out.length < max) {
|
||||
const e = norm(cur.sponsorEmail); if (seen.has(e)) break; seen.add(e);
|
||||
const sp = await get(e); if (!sp) break;
|
||||
out.push(sp); cur = sp;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
// bulk load from an InstantAdPay export: [{email, username, wallet, sponsorEmail, joinedAt}]
|
||||
async function importRows(rows, property) {
|
||||
let n = 0; for (const r of rows || []) { if (await upsert(Object.assign({ firstProperty: property || 'instantadpay' }, r))) n++; } return n;
|
||||
}
|
||||
|
||||
module.exports = { init, get, byWallet, byUsername, upsert, all, count, chainUp, importRows };
|
||||
Reference in New Issue
Block a user