Holding tank: unsponsored free members, first-come adoption (own $20 buy required, 2 open, 7-day window, twice max), release to tank, admin view; PIF wallet-to-wallet POL gift with logging
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,7 @@ const spaces = require('./spaces'); // DO Spaces video storage (inert unless DO_
|
||||
let QR = null; try { QR = require('qrcode'); } catch (e) { /* optional */ }
|
||||
const chatbot = require('./chatbot');
|
||||
const coach = require('./coach'); // coaching view, nudges, digest, prospects, link stats
|
||||
const tank = require('./tank'); // holding tank: unsponsored free members, adoptions, pay-it-forward
|
||||
const burner = require('./burner'); // automatic on-chain credit burns (inert without ENGINE_KEY)
|
||||
|
||||
const PORT = Number(process.env.PORT || 3000);
|
||||
@@ -301,6 +302,8 @@ async function boot() {
|
||||
setInterval(() => ads.scheduleSweep().catch(e => console.error('schedule sweep', e.message)), 5 * 60 * 1000); // scheduled starts/ends
|
||||
// follow-up email sequence: send whatever came due (every 10 min, first pass shortly after boot)
|
||||
coach.init({ dataDir: DATA_DIR, chain, accounts, mailer });
|
||||
tank.init({ dataDir: DATA_DIR, chain, accounts, messages, mailer, site: 'https://instantadpay.com' });
|
||||
setInterval(() => tank.sweep().catch(e => console.error('tank sweep', e.message)), 60 * 60 * 1000); // adoptions past their 7-day window
|
||||
burner.init({ chain, ads });
|
||||
setTimeout(() => coach.nudgeTick().catch(e => console.error('coach', e.message)), 90 * 1000);
|
||||
setInterval(() => coach.nudgeTick().catch(e => console.error('coach', e.message)), 60 * 60 * 1000);
|
||||
@@ -980,6 +983,37 @@ const server = http.createServer(async (req, res) => {
|
||||
return json(res, r.error ? 400 : 200, r);
|
||||
}
|
||||
// -- coaching: every direct's ladder rung, stalled flag, and what to say
|
||||
// -- holding tank: waiting members, my adoptions, adopt, release (pay it forward)
|
||||
if (p === '/api/my/tank' && req.method === 'GET') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
return json(res, 200, await tank.view(s.email));
|
||||
}
|
||||
if (p === '/api/my/tank/adopt' && req.method === 'POST') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
const b = await readBody(req);
|
||||
const r = await tank.adopt(s.email, b.who, b.note);
|
||||
return json(res, r.error ? 400 : 200, r);
|
||||
}
|
||||
if (p === '/api/my/tank/release' && req.method === 'POST') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
const b = await readBody(req);
|
||||
const r = await tank.release(s.email, b.email);
|
||||
return json(res, r.error ? 400 : 200, r);
|
||||
}
|
||||
if (p === '/api/my/gift' && req.method === 'POST') { // PIF: log a wallet-to-wallet POL gift and tell the recipient
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
const b = await readBody(req);
|
||||
const r = await tank.recordGift(s.email, b.email, b.tx, b.pol);
|
||||
return json(res, r.error ? 400 : 200, r);
|
||||
}
|
||||
if (p === '/api/admin/tank' && req.method === 'GET') {
|
||||
if (!isAdmin(req)) return json(res, 401, { error: 'auth' });
|
||||
return json(res, 200, await tank.adminView());
|
||||
}
|
||||
if (p === '/api/my/coach' && req.method === 'GET') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||
|
||||
Reference in New Issue
Block a user