f053c1befa
Zero-dependency Node server on the RM Circle pattern. Chain config lives in the volume so the same code runs the Amoy dress rehearsal and mainnet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
50 lines
1.9 KiB
JavaScript
50 lines
1.9 KiB
JavaScript
// Site-side member records for InstantAdPay.
|
|
// The chain is the source of truth for money, credits, and qualification;
|
|
// this module holds only what the chain doesn't: free members who haven't
|
|
// touched the chain yet, sponsor attribution before first purchase (spec §4),
|
|
// display handles, and join stats. Wiping this file = the clean reset between
|
|
// the Amoy dress rehearsal and mainnet launch.
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
let DATA_DIR = null;
|
|
const FILE = () => path.join(DATA_DIR, 'accounts.json');
|
|
let db = { v: 1, byAddress: {}, joins: 0 };
|
|
|
|
function load() {
|
|
try { db = JSON.parse(fs.readFileSync(FILE(), 'utf8')); } catch (e) {}
|
|
if (!db || db.v !== 1) db = { v: 1, byAddress: {}, joins: 0 };
|
|
}
|
|
function save() {
|
|
try {
|
|
const tmp = FILE() + '.tmp';
|
|
fs.writeFileSync(tmp, JSON.stringify(db));
|
|
fs.renameSync(tmp, FILE());
|
|
} catch (e) { console.error('accounts save failed', e.message); }
|
|
}
|
|
function init(opts) { DATA_DIR = opts.dataDir; load(); }
|
|
|
|
function get(address) { return db.byAddress[(address || '').toLowerCase()] || null; }
|
|
function upsert(address, fields) {
|
|
const a = (address || '').toLowerCase();
|
|
if (!/^0x[0-9a-f]{40}$/.test(a)) return null;
|
|
const cur = db.byAddress[a] || { created: Date.now() };
|
|
db.byAddress[a] = Object.assign(cur, fields || {});
|
|
save();
|
|
return db.byAddress[a];
|
|
}
|
|
// Sponsor attribution: first touch wins, written on-chain at the member's
|
|
// first purchase/activation and permanent from then on.
|
|
function attributeSponsor(address, sponsorId) {
|
|
const a = (address || '').toLowerCase();
|
|
const cur = get(a);
|
|
if (cur && cur.sponsorId) return cur.sponsorId; // first touch already set
|
|
const id = Number(sponsorId) || 0;
|
|
upsert(a, { sponsorId: id });
|
|
db.joins += 1; save();
|
|
return id;
|
|
}
|
|
function count() { return Object.keys(db.byAddress).length; }
|
|
|
|
module.exports = { init, get, upsert, attributeSponsor, count };
|