Onsite solo ads: inbox delivery with read rewards, composer, homepage format live

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-05 15:25:00 -05:00
parent 5c945a1b09
commit f6a3befe09
9 changed files with 373 additions and 21 deletions
+31 -1
View File
@@ -392,6 +392,7 @@ const server = http.createServer(async (req, res) => {
refCode: (acct && acct.code) || null, credits: 0, buyerCount: 0,
earnedWei: '0', earnCount: 0, referrals: [], welcomeCredits: 0 };
if (out.email) out.welcomeCredits = await ads.grantWelcome(out.email); // idempotent lazy grant
if (out.email) out.inboxUnread = await ads.unreadCount(out.email); // delivers pending solos too
if (memberId) {
try {
const mm = await chain.member(memberId);
@@ -504,6 +505,35 @@ const server = http.createServer(async (req, res) => {
const r = await ads.claimDaily(s.email);
return json(res, r.error ? 400 : 200, r);
}
// -- onsite solo ads: member inbox with read rewards
if (p === '/api/my/inbox' && req.method === 'GET') {
const s = await auth.fromRequest(req);
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
const r = await ads.inboxList(s.email);
const names = await accounts.namesForMembers([...new Set(r.items.map(i => i.fromMemberId).filter(Boolean))]);
for (const i of r.items) i.fromName = (i.fromMemberId && names[i.fromMemberId]) ? '@' + names[i.fromMemberId]
: i.fromMemberId ? 'member #' + i.fromMemberId : 'a member';
return json(res, 200, r);
}
m = /^\/api\/my\/inbox\/(\d+)$/.exec(p);
if (m && req.method === 'GET') {
const s = await auth.fromRequest(req);
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
const r = await ads.inboxOpen(s.email, m[1]);
if (!r.error) {
const names = r.fromMemberId ? await accounts.namesForMembers([r.fromMemberId]) : {};
r.fromName = (r.fromMemberId && names[r.fromMemberId]) ? '@' + names[r.fromMemberId]
: r.fromMemberId ? 'member #' + r.fromMemberId : 'a member';
}
return json(res, r.error ? 404 : 200, r);
}
m = /^\/api\/my\/inbox\/(\d+)\/claim$/.exec(p);
if (m && req.method === 'POST') {
const s = await auth.fromRequest(req);
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
const r = await ads.claimSoloRead(s.email, m[1]);
return json(res, r.error ? 400 : 200, r);
}
if (p === '/api/my/activity' && req.method === 'GET') {
const s = await auth.fromRequest(req);
if (!s) return json(res, 401, { error: 'Sign in first.' });
@@ -545,7 +575,7 @@ const server = http.createServer(async (req, res) => {
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
const memberId = await auth.refreshMemberId(s); // 0 is fine: earned credits fund banner/text
const b = await readBody(req);
if (b.type !== 'login') { // surf views frame the target: catch frame-breakers at the door
if (!['login', 'solo'].includes(String(b.type || ''))) { // surf views frame the target: catch frame-breakers at the door (login + solo are click-through only)
const fc = await frameCheck(b.targetUrl);
if (!fc.ok) return json(res, 400, { error: fc.reason });
}