523d57624e
Hand-off sign-in from InstantAdPay (lib/sso.js: HMAC token, five minutes, single use; no sign-up, no mailer), missions with per-member per-visit proof codes (lib/missions.js: the embed is answered only from the mission's own origin, only after the dwell; hiding place rotates by day and member), weighted-low rewards with a daily cap that queues rather than refuses (lib/rewards.js), the faucet sender on ethers with a low-balance alert (lib/faucet.js), the one-line embed for the sites (public/embed.js), the hunter board, the public ledger, an admin page, and the site's own look. Telegram is behind the OUTBOUND gate like everything else. 13-check end-to-end test in test/run.js. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
29 lines
1.0 KiB
JavaScript
29 lines
1.0 KiB
JavaScript
// Tiny JSON store on the volume. Every collection is one file; writes are atomic (tmp + rename).
|
|
// PolHunter's data is small (missions, completions, payouts) and a member's balance never lives
|
|
// here, so a database would be ceremony. If it ever grows, this is the one file to swap.
|
|
'use strict';
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
let DIR = null;
|
|
function init(dir) { DIR = dir; fs.mkdirSync(DIR, { recursive: true }); }
|
|
const file = name => path.join(DIR, name + '.json');
|
|
|
|
function read(name, fallback) {
|
|
try { return JSON.parse(fs.readFileSync(file(name), 'utf8')); } catch (e) { return fallback; }
|
|
}
|
|
function write(name, data) {
|
|
const f = file(name), tmp = f + '.tmp';
|
|
fs.writeFileSync(tmp, JSON.stringify(data));
|
|
fs.renameSync(tmp, f);
|
|
}
|
|
// read-modify-write in one place so callers never race themselves
|
|
function update(name, fallback, fn) {
|
|
const cur = read(name, fallback);
|
|
const out = fn(cur) || cur;
|
|
write(name, out);
|
|
return out;
|
|
}
|
|
|
|
module.exports = { init, read, write, update };
|