010e8d7ffc
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
56 lines
3.3 KiB
JavaScript
56 lines
3.3 KiB
JavaScript
// 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 };
|