Admin-only Suite level preview for testing
suiteLevelOverride ('id:level,...') raises ONLY what the tool wall unlocks,
so the owner can walk every tier without buying upgrades to QA his own
product. Nothing financial moves: payouts, contract reads and every number
shown to a member still come from the real level, kept on trueLevel. Clones
rather than mutates, since the member object is a shared cache entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -668,7 +668,24 @@ async function handleApi(req,res,pathname){
|
|||||||
const allow=String(cfg.suiteAllowlist||'').split(',').map(x=>Number(x.trim())).filter(Boolean);
|
const allow=String(cfg.suiteAllowlist||'').split(',').map(x=>Number(x.trim())).filter(Boolean);
|
||||||
const beta=allow.length>0;
|
const beta=allow.length>0;
|
||||||
const allowed=!beta||allow.includes(Number(d.id));
|
const allowed=!beta||allow.includes(Number(d.id));
|
||||||
return {d,inOrg,beta,allowed};
|
|
||||||
|
// Admin-only tool preview. `suiteLevelOverride` is "id:level,id:level" and
|
||||||
|
// ONLY raises what the tool wall unlocks, so the owner can walk every tier
|
||||||
|
// without buying upgrades to test his own product. It touches nothing
|
||||||
|
// financial: payouts, the contract and every number shown to a member still
|
||||||
|
// come from the real level, which is kept on trueLevel. We clone rather than
|
||||||
|
// mutate because `d` is the shared member cache.
|
||||||
|
let dd=d;
|
||||||
|
const ovMap={};
|
||||||
|
String(cfg.suiteLevelOverride||'').split(',').forEach(pair=>{
|
||||||
|
const [k,v]=String(pair).split(':').map(x=>Number(String(x).trim()));
|
||||||
|
if(k&&v>=1&&v<=8)ovMap[k]=v;
|
||||||
|
});
|
||||||
|
const ov=ovMap[Number(d.id)];
|
||||||
|
if(ov&&ov>Number(d.level||0)){
|
||||||
|
dd=Object.assign({},d,{level:ov,trueLevel:Number(d.level||0),levelOverridden:true});
|
||||||
|
}
|
||||||
|
return {d:dd,inOrg,beta,allowed};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Page Builder ─────────────────────────────────────────────────────────
|
// ── Page Builder ─────────────────────────────────────────────────────────
|
||||||
@@ -1205,7 +1222,7 @@ async function handleApi(req,res,pathname){
|
|||||||
const maxOrder=sponsors.reduce((m,s)=>Math.max(m,s.sortOrder||0),0);sponsors.push({id:String(id).trim(),name:String(name).trim(),parentId:String(parentId||'').trim(),directs:0,level,status:sponsors.some(s=>s.status==='active')?'waiting':'active',sortOrder:maxOrder+10,clicks:0,notes:String(notes||'').trim(),email:String(email||'').trim().slice(0,120)});sponsors=normalizeStatuses(sponsors);saveSponsors(sponsors);return json(res,201,{sponsors});
|
const maxOrder=sponsors.reduce((m,s)=>Math.max(m,s.sortOrder||0),0);sponsors.push({id:String(id).trim(),name:String(name).trim(),parentId:String(parentId||'').trim(),directs:0,level,status:sponsors.some(s=>s.status==='active')?'waiting':'active',sortOrder:maxOrder+10,clicks:0,notes:String(notes||'').trim(),email:String(email||'').trim().slice(0,120)});sponsors=normalizeStatuses(sponsors);saveSponsors(sponsors);return json(res,201,{sponsors});
|
||||||
}
|
}
|
||||||
if(req.method==='PATCH'&&pathname==='/api/admin/config'){
|
if(req.method==='PATCH'&&pathname==='/api/admin/config'){
|
||||||
const b=await bodyJson(req),cur=getConfig(),next={...cur};for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','showSponsorName','showQueueProgress','bemobPostbackUrl','telegramBotToken','companionBotToken','miniAppShortName','telegramChatId','telegramTopicId','telegramRecruitTopicId','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','ctbOfferPostbackUrl','ctbOfferSecret','recruitCtaUrl','walletNotice','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId','dappFallbackPublic','moonpayPublicKey','moonpaySecretKey','publicRotationMode','publicRotationRootId','rotationExcludeIds','suiteAllowlist','suiteToolsInAlerts'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;next.updatedAt=new Date().toISOString();writeJson(CONFIG_FILE,next);return json(res,200,{config:next});
|
const b=await bodyJson(req),cur=getConfig(),next={...cur};for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','showSponsorName','showQueueProgress','bemobPostbackUrl','telegramBotToken','companionBotToken','miniAppShortName','telegramChatId','telegramTopicId','telegramRecruitTopicId','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','ctbOfferPostbackUrl','ctbOfferSecret','recruitCtaUrl','walletNotice','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId','dappFallbackPublic','moonpayPublicKey','moonpaySecretKey','publicRotationMode','publicRotationRootId','rotationExcludeIds','suiteAllowlist','suiteToolsInAlerts','suiteLevelOverride'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;next.updatedAt=new Date().toISOString();writeJson(CONFIG_FILE,next);return json(res,200,{config:next});
|
||||||
}
|
}
|
||||||
const m=pathname.match(/^\/api\/admin\/sponsors\/([^/]+)(?:\/(increment|activate|qualify|reset|move))?$/);
|
const m=pathname.match(/^\/api\/admin\/sponsors\/([^/]+)(?:\/(increment|activate|qualify|reset|move))?$/);
|
||||||
if(m){const id=decodeURIComponent(m[1]),action=m[2]||null;let sponsors=getSponsors(),idx=sponsors.findIndex(s=>s.id===id);if(idx<0)return json(res,404,{error:'Sponsor not found.'});
|
if(m){const id=decodeURIComponent(m[1]),action=m[2]||null;let sponsors=getSponsors(),idx=sponsors.findIndex(s=>s.id===id);if(idx<0)return json(res,404,{error:'Sponsor not found.'});
|
||||||
|
|||||||
Reference in New Issue
Block a user