diff --git a/ads.js b/ads.js index 2633ed9..8d14ecb 100644 Binary files a/ads.js and b/ads.js differ diff --git a/db.js b/db.js index 153ba92..db73d39 100644 --- a/db.js +++ b/db.js @@ -120,6 +120,9 @@ async function bootstrap() { await alterSafe('ALTER TABLE campaigns ADD COLUMN expires BIGINT NULL'); // featured rotation end time await alterSafe('ALTER TABLE campaigns ADD COLUMN starts BIGINT NULL'); // featured run start (booked day) await alterSafe('ALTER TABLE campaigns ADD COLUMN house TINYINT NOT NULL DEFAULT 0'); // admin house ad: free, never charged + await alterSafe('ALTER TABLE campaigns ADD COLUMN daily_cap INT NULL'); // optional credits/day pacing (banner + text) + await alterSafe('ALTER TABLE campaigns ADD COLUMN day_spent INT NOT NULL DEFAULT 0'); + await alterSafe("ALTER TABLE campaigns ADD COLUMN day_key CHAR(10) NULL"); // linked positions: extra wallets owned by one email account (Qualified Start). // Each is its own on-chain member sponsored by the account's main member. await q(`CREATE TABLE IF NOT EXISTS positions ( diff --git a/nas.js b/nas.js index b012f0f..8d8188b 100644 --- a/nas.js +++ b/nas.js @@ -58,7 +58,8 @@ async function pushCampaign(c, opts = {}) { const kind = nasKind(c.type); if (!kind) return { skipped: 'type' }; const budgetLeft = c.budget - (c.spent || 0); - const assigned = Math.max(1, Math.floor(budgetLeft * impressionsPerCredit(c.type))); + const window = c.dailyCap ? Math.min(c.dailyCap, budgetLeft) : budgetLeft; // capped campaigns start with one day's allowance + const assigned = Math.max(1, Math.floor(window * impressionsPerCredit(c.type))); const token = 'iap_' + crypto.randomBytes(8).toString('hex'); // manage token = Username const now = new Date(); const days = Number(opts.days || 30); diff --git a/public/assets/my.js b/public/assets/my.js index 60db133..993e071 100644 --- a/public/assets/my.js +++ b/public/assets/my.js @@ -608,7 +608,7 @@ tbl.innerHTML = '
| Name | Type | Views here | Network views | Clicks | ' + 'Spent | Budget | Status | |
|---|---|---|---|---|---|---|---|---|
| ' + c.name + ' | ' - + '' + c.type + (c.type === 'banner' && c.width ? ' ' + c.width + '×' + c.height + '' : '') + ' | ' + + '' + c.type + (c.type === 'banner' && c.width ? ' ' + c.width + '×' + c.height + '' : '') + (c.dailyCap ? ' cap ' + c.dailyCap + '/day' : '') + ' | ' + '' + c.imps.toLocaleString() + ' | ' + '' + (['banner', 'text'].includes(c.type) ? (c.impsNas || 0).toLocaleString() : 'n/a') + ' | ' + '' + c.clicks + ' | ' @@ -708,6 +708,7 @@ // fixed-cost types derive their spend (featured = day slots, visits = flat pack), // so hide the free-form Budget field for them to avoid confusion $('cBudgetRow').hidden = (t === 'featured' || t === 'visits'); + if ($('cCapRow')) $('cCapRow').hidden = !(t === 'banner' || t === 'text'); // solo ads have a real floor (5cr x 10 deliveries): default to 50 so 10 isn't rejected if (t === 'solo' && (!$('cBudget').value || Number($('cBudget').value) < 50)) $('cBudget').value = (lastRates && lastRates.soloCostPerRecipient ? lastRates.soloCostPerRecipient : 5) * 10; if (t === 'visits') visitHint(); @@ -801,12 +802,13 @@ videoW: cVidDims ? cVidDims.w : null, videoH: cVidDims ? cVidDims.h : null, days: Number($('cFeatDays').value), startDay: featStartDay, count: Number($('cVisitCount').value), + dailyCap: ($('cDailyCap') && (t === 'banner' || t === 'text')) ? Number($('cDailyCap').value) || 0 : 0, ctaLabel: isVideo ? $('cVideoCta').value : $('cCtaLabel').value, budget: isFeat ? (Number($('cFeatDays').value) * (lastRates.featuredPerDay || 40)) : t === 'visits' ? (Number($('cVisitCount').value) * (lastRates.visitCostPerVisit || 3)) : Number($('cBudget').value) }); IAP.status('Campaign is live. It starts serving right away.', 'ok'); // clear EVERY field so no target/creative carries into the next campaign ['cName', 'cBudget', 'cTarget', 'cImage', 'cTitle', 'cBody', 'cCtaLabel', - 'cVideoUrl', 'cVideoTitle', 'cVideoCta', 'cVisitTitle', 'cVisitCount', 'cFeatTitle'] + 'cVideoUrl', 'cVideoTitle', 'cVideoCta', 'cVisitTitle', 'cVisitCount', 'cFeatTitle', 'cDailyCap'] .forEach(id => { if ($(id)) $(id).value = ''; }); $('cSoloEd').innerHTML = ''; if ($('cSoloRaw')) $('cSoloRaw').value = ''; $('cVideoInfo').textContent = ''; $('cVideoPrev').hidden = true; $('cVideoPrev').innerHTML = ''; diff --git a/public/my.html b/public/my.html index edd044f..1df5c3f 100644 --- a/public/my.html +++ b/public/my.html @@ -431,6 +431,8 @@ +