web-agent-bridge 2.3.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ar.md +524 -31
- package/README.md +592 -47
- package/bin/agent-runner.js +10 -1
- package/package.json +1 -1
- package/public/agent-workspace.html +347 -0
- package/public/browser.html +484 -0
- package/public/css/agent-workspace.css +1713 -0
- package/public/index.html +94 -0
- package/public/js/agent-workspace.js +1740 -0
- package/sdk/index.d.ts +253 -0
- package/sdk/index.js +360 -1
- package/sdk/package.json +1 -1
- package/server/config/secrets.js +13 -5
- package/server/control-plane/index.js +301 -0
- package/server/data-plane/index.js +354 -0
- package/server/index.js +185 -4
- package/server/llm/index.js +404 -0
- package/server/middleware/adminAuth.js +6 -1
- package/server/middleware/auth.js +11 -2
- package/server/middleware/rateLimits.js +78 -2
- package/server/migrations/003_ads_integer_cents.sql +33 -0
- package/server/models/db.js +126 -25
- package/server/observability/index.js +394 -0
- package/server/protocol/capabilities.js +223 -0
- package/server/protocol/index.js +243 -0
- package/server/protocol/schema.js +584 -0
- package/server/registry/index.js +326 -0
- package/server/routes/admin.js +16 -2
- package/server/routes/ads.js +130 -0
- package/server/routes/agent-workspace.js +378 -0
- package/server/routes/api.js +21 -2
- package/server/routes/auth.js +26 -6
- package/server/routes/runtime.js +725 -0
- package/server/routes/sovereign.js +78 -0
- package/server/routes/universal.js +177 -0
- package/server/routes/wab-api.js +20 -5
- package/server/runtime/event-bus.js +210 -0
- package/server/runtime/index.js +233 -0
- package/server/runtime/sandbox.js +266 -0
- package/server/runtime/scheduler.js +395 -0
- package/server/runtime/state-manager.js +188 -0
- package/server/security/index.js +355 -0
- package/server/services/agent-chat.js +506 -0
- package/server/services/agent-symphony.js +6 -0
- package/server/services/agent-tasks.js +1807 -0
- package/server/services/fairness-engine.js +409 -0
- package/server/services/plugins.js +27 -3
- package/server/services/price-intelligence.js +565 -0
- package/server/services/price-shield.js +1137 -0
- package/server/services/search-engine.js +357 -0
- package/server/services/security.js +513 -0
- package/server/services/universal-scraper.js +661 -0
- package/server/ws.js +61 -1
|
@@ -1,9 +1,75 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Comprehensive rate limits for all security-sensitive endpoints.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const rateLimit = require('express-rate-limit');
|
|
6
6
|
|
|
7
|
+
// ─── Auth endpoints ──────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const authLimiter = rateLimit({
|
|
10
|
+
windowMs: 15 * 60 * 1000,
|
|
11
|
+
max: 10,
|
|
12
|
+
standardHeaders: true,
|
|
13
|
+
legacyHeaders: false,
|
|
14
|
+
message: { error: 'Too many authentication attempts, please try again later' }
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const registerLimiter = rateLimit({
|
|
18
|
+
windowMs: 60 * 60 * 1000,
|
|
19
|
+
max: 5,
|
|
20
|
+
standardHeaders: true,
|
|
21
|
+
legacyHeaders: false,
|
|
22
|
+
message: { error: 'Too many registration attempts, please try again later' }
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const adminLoginLimiter = rateLimit({
|
|
26
|
+
windowMs: 15 * 60 * 1000,
|
|
27
|
+
max: 5,
|
|
28
|
+
standardHeaders: true,
|
|
29
|
+
legacyHeaders: false,
|
|
30
|
+
message: { error: 'Too many admin login attempts, please try again later' }
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// ─── WAB API endpoints ───────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
const wabAuthenticateLimiter = rateLimit({
|
|
36
|
+
windowMs: 15 * 60 * 1000,
|
|
37
|
+
max: 20,
|
|
38
|
+
standardHeaders: true,
|
|
39
|
+
legacyHeaders: false,
|
|
40
|
+
keyGenerator: (req) => `${req.ip}:${req.body?.siteId || req.body?.apiKey || 'anon'}`,
|
|
41
|
+
message: { error: 'Too many WAB authentication attempts' }
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const wabActionLimiter = rateLimit({
|
|
45
|
+
windowMs: 60 * 1000,
|
|
46
|
+
max: 60,
|
|
47
|
+
standardHeaders: true,
|
|
48
|
+
legacyHeaders: false,
|
|
49
|
+
keyGenerator: (req) => `${req.ip}:${req.wabSession?.siteId || 'anon'}`,
|
|
50
|
+
message: { error: 'Too many action requests, please slow down' }
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ─── General API endpoints ───────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
const apiLimiter = rateLimit({
|
|
56
|
+
windowMs: 60 * 1000,
|
|
57
|
+
max: 100,
|
|
58
|
+
standardHeaders: true,
|
|
59
|
+
legacyHeaders: false,
|
|
60
|
+
message: { error: 'Too many requests, please try again later' }
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const searchLimiter = rateLimit({
|
|
64
|
+
windowMs: 60 * 1000,
|
|
65
|
+
max: 30,
|
|
66
|
+
standardHeaders: true,
|
|
67
|
+
legacyHeaders: false,
|
|
68
|
+
message: { error: 'Too many search requests' }
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// ─── License endpoints (existing) ────────────────────────────────────
|
|
72
|
+
|
|
7
73
|
const licenseTokenLimiter = rateLimit({
|
|
8
74
|
windowMs: 15 * 60 * 1000,
|
|
9
75
|
max: 30,
|
|
@@ -21,4 +87,14 @@ const licenseTrackLimiter = rateLimit({
|
|
|
21
87
|
message: { error: 'Too many track requests, please try again later' }
|
|
22
88
|
});
|
|
23
89
|
|
|
24
|
-
module.exports = {
|
|
90
|
+
module.exports = {
|
|
91
|
+
authLimiter,
|
|
92
|
+
registerLimiter,
|
|
93
|
+
adminLoginLimiter,
|
|
94
|
+
wabAuthenticateLimiter,
|
|
95
|
+
wabActionLimiter,
|
|
96
|
+
apiLimiter,
|
|
97
|
+
searchLimiter,
|
|
98
|
+
licenseTokenLimiter,
|
|
99
|
+
licenseTrackLimiter,
|
|
100
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- Migration 003: Convert ads financial columns from REAL to INTEGER (cents)
|
|
2
|
+
-- This avoids floating-point precision issues in billing calculations.
|
|
3
|
+
--
|
|
4
|
+
-- NOTE: The wab_ads table in db.js now creates with INTEGER columns directly.
|
|
5
|
+
-- This migration only matters for databases created before this change.
|
|
6
|
+
-- On fresh databases, db.js already has the correct schema, so this is a no-op.
|
|
7
|
+
-- On existing databases, this migration was already applied.
|
|
8
|
+
|
|
9
|
+
-- Ensure the table and index exist (idempotent)
|
|
10
|
+
CREATE TABLE IF NOT EXISTS wab_ads (
|
|
11
|
+
id TEXT PRIMARY KEY,
|
|
12
|
+
title TEXT NOT NULL,
|
|
13
|
+
description TEXT,
|
|
14
|
+
image_url TEXT,
|
|
15
|
+
target_url TEXT NOT NULL,
|
|
16
|
+
advertiser_name TEXT NOT NULL,
|
|
17
|
+
advertiser_email TEXT NOT NULL,
|
|
18
|
+
status TEXT DEFAULT 'pending' CHECK(status IN ('pending','approved','rejected','paused','expired')),
|
|
19
|
+
position TEXT DEFAULT 'new-tab' CHECK(position IN ('new-tab','sidebar','search')),
|
|
20
|
+
budget_cents INTEGER DEFAULT 0,
|
|
21
|
+
spent_cents INTEGER DEFAULT 0,
|
|
22
|
+
cpc_cents INTEGER DEFAULT 5,
|
|
23
|
+
cpi_cents INTEGER DEFAULT 1,
|
|
24
|
+
impressions INTEGER DEFAULT 0,
|
|
25
|
+
clicks INTEGER DEFAULT 0,
|
|
26
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
27
|
+
approved_by TEXT,
|
|
28
|
+
approved_at TEXT,
|
|
29
|
+
expires_at TEXT,
|
|
30
|
+
FOREIGN KEY (approved_by) REFERENCES admins(id)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_wab_ads_status ON wab_ads(status);
|
package/server/models/db.js
CHANGED
|
@@ -168,6 +168,43 @@ db.exec(`
|
|
|
168
168
|
CREATE INDEX IF NOT EXISTS idx_stripe_subs_user ON stripe_subscriptions(user_id);
|
|
169
169
|
CREATE INDEX IF NOT EXISTS idx_payments_user ON payments(user_id);
|
|
170
170
|
CREATE INDEX IF NOT EXISTS idx_notifications_user ON notifications_log(user_id);
|
|
171
|
+
|
|
172
|
+
CREATE TABLE IF NOT EXISTS wab_ads (
|
|
173
|
+
id TEXT PRIMARY KEY,
|
|
174
|
+
title TEXT NOT NULL,
|
|
175
|
+
description TEXT,
|
|
176
|
+
image_url TEXT,
|
|
177
|
+
target_url TEXT NOT NULL,
|
|
178
|
+
advertiser_name TEXT NOT NULL,
|
|
179
|
+
advertiser_email TEXT NOT NULL,
|
|
180
|
+
status TEXT DEFAULT 'pending' CHECK(status IN ('pending','approved','rejected','paused','expired')),
|
|
181
|
+
position TEXT DEFAULT 'new-tab' CHECK(position IN ('new-tab','sidebar','search')),
|
|
182
|
+
budget_cents INTEGER DEFAULT 0,
|
|
183
|
+
spent_cents INTEGER DEFAULT 0,
|
|
184
|
+
cpc_cents INTEGER DEFAULT 5,
|
|
185
|
+
cpi_cents INTEGER DEFAULT 1,
|
|
186
|
+
impressions INTEGER DEFAULT 0,
|
|
187
|
+
clicks INTEGER DEFAULT 0,
|
|
188
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
189
|
+
approved_by TEXT,
|
|
190
|
+
approved_at TEXT,
|
|
191
|
+
expires_at TEXT,
|
|
192
|
+
FOREIGN KEY (approved_by) REFERENCES admins(id)
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
CREATE TABLE IF NOT EXISTS ad_events (
|
|
196
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
197
|
+
ad_id TEXT NOT NULL,
|
|
198
|
+
event_type TEXT NOT NULL CHECK(event_type IN ('impression','click')),
|
|
199
|
+
platform TEXT DEFAULT 'browser',
|
|
200
|
+
ip_hash TEXT,
|
|
201
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
202
|
+
FOREIGN KEY (ad_id) REFERENCES wab_ads(id) ON DELETE CASCADE
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
CREATE INDEX IF NOT EXISTS idx_wab_ads_status ON wab_ads(status);
|
|
206
|
+
CREATE INDEX IF NOT EXISTS idx_ad_events_ad ON ad_events(ad_id);
|
|
207
|
+
CREATE INDEX IF NOT EXISTS idx_ad_events_created ON ad_events(created_at);
|
|
171
208
|
`);
|
|
172
209
|
|
|
173
210
|
function generateLicenseKey() {
|
|
@@ -218,6 +255,7 @@ const findSitesByUser = db.prepare(`SELECT * FROM sites WHERE user_id = ? ORDER
|
|
|
218
255
|
const findSiteById = db.prepare(`SELECT * FROM sites WHERE id = ?`);
|
|
219
256
|
const findSiteByLicense = db.prepare(`SELECT * FROM sites WHERE license_key = ? AND active = 1`);
|
|
220
257
|
const findSiteByDomainAndLicense = db.prepare(`SELECT * FROM sites WHERE domain = ? AND license_key = ? AND active = 1`);
|
|
258
|
+
const findSiteByDomain = db.prepare(`SELECT * FROM sites WHERE domain = ? AND active = 1 LIMIT 1`);
|
|
221
259
|
const updateSiteConfig = db.prepare(`UPDATE sites SET config = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`);
|
|
222
260
|
const updateSiteTier = db.prepare(`UPDATE sites SET tier = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`);
|
|
223
261
|
const deleteSite = db.prepare(`UPDATE sites SET active = 0, updated_at = datetime('now') WHERE id = ? AND user_id = ?`);
|
|
@@ -287,36 +325,18 @@ function verifyLicense(domain, licenseKey) {
|
|
|
287
325
|
}
|
|
288
326
|
|
|
289
327
|
// ─── Admin Operations ─────────────────────────────────────────────────
|
|
290
|
-
function normalizeAdminEmail(email) {
|
|
291
|
-
if (email == null) return '';
|
|
292
|
-
return String(email).trim().toLowerCase();
|
|
293
|
-
}
|
|
294
|
-
|
|
295
328
|
function createAdmin({ email, password, name, role }) {
|
|
296
|
-
const normEmail = normalizeAdminEmail(email);
|
|
297
|
-
if (!normEmail) throw new Error('Admin email required');
|
|
298
329
|
const id = uuidv4();
|
|
299
330
|
const hashed = bcrypt.hashSync(password, 12);
|
|
300
|
-
db.prepare(`INSERT INTO admins (id, email, password, name, role) VALUES (?, ?, ?, ?, ?)`).run(id,
|
|
301
|
-
return { id, email
|
|
331
|
+
db.prepare(`INSERT INTO admins (id, email, password, name, role) VALUES (?, ?, ?, ?, ?)`).run(id, email, hashed, name, role || 'admin');
|
|
332
|
+
return { id, email, name, role: role || 'admin' };
|
|
302
333
|
}
|
|
303
334
|
|
|
304
335
|
function loginAdmin({ email, password }) {
|
|
305
|
-
const
|
|
306
|
-
if (!normEmail || password == null || password === '') return null;
|
|
307
|
-
const admin = db.prepare(`SELECT * FROM admins WHERE LOWER(TRIM(email)) = ?`).get(normEmail);
|
|
336
|
+
const admin = db.prepare(`SELECT * FROM admins WHERE email = ?`).get(email);
|
|
308
337
|
if (!admin) return null;
|
|
309
338
|
if (!bcrypt.compareSync(password, admin.password)) return null;
|
|
310
|
-
return { id: admin.id, email:
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/** CLI / ops only: set password for an existing admin row by email. */
|
|
314
|
-
function resetAdminPassword(email, newPassword) {
|
|
315
|
-
const normEmail = normalizeAdminEmail(email);
|
|
316
|
-
if (!normEmail) return false;
|
|
317
|
-
const hashed = bcrypt.hashSync(newPassword, 12);
|
|
318
|
-
const r = db.prepare(`UPDATE admins SET password = ? WHERE LOWER(TRIM(email)) = ?`).run(hashed, normEmail);
|
|
319
|
-
return r.changes > 0;
|
|
339
|
+
return { id: admin.id, email: admin.email, name: admin.name, role: admin.role };
|
|
320
340
|
}
|
|
321
341
|
|
|
322
342
|
function findAdminById(id) {
|
|
@@ -331,7 +351,7 @@ function maybeBootstrapAdmin() {
|
|
|
331
351
|
if (isTest) return;
|
|
332
352
|
const count = db.prepare(`SELECT COUNT(*) as c FROM admins`).get().c;
|
|
333
353
|
if (count > 0) return;
|
|
334
|
-
const email =
|
|
354
|
+
const email = process.env.BOOTSTRAP_ADMIN_EMAIL;
|
|
335
355
|
const password = process.env.BOOTSTRAP_ADMIN_PASSWORD;
|
|
336
356
|
if (!email || !password) {
|
|
337
357
|
console.warn('[WAB] No admin accounts. Set BOOTSTRAP_ADMIN_EMAIL and BOOTSTRAP_ADMIN_PASSWORD for first boot, or run: node scripts/create-admin.js <email> <password>');
|
|
@@ -524,6 +544,77 @@ function setPlatformSetting(key, value) {
|
|
|
524
544
|
db.prepare(`INSERT OR REPLACE INTO platform_settings (key, value, updated_at) VALUES (?, ?, datetime('now'))`).run(key, value);
|
|
525
545
|
}
|
|
526
546
|
|
|
547
|
+
// ─── Ads Operations ──────────────────────────────────────────────────
|
|
548
|
+
function submitAd({ title, description, imageUrl, targetUrl, advertiserName, advertiserEmail, position, budgetCents, cpcCents, cpiCents, expiresAt }) {
|
|
549
|
+
const id = uuidv4();
|
|
550
|
+
db.prepare(`INSERT INTO wab_ads (id, title, description, image_url, target_url, advertiser_name, advertiser_email, position, budget_cents, cpc_cents, cpi_cents, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, title, description || '', imageUrl || '', targetUrl, advertiserName, advertiserEmail, position || 'new-tab', budgetCents || 0, cpcCents || 5, cpiCents || 1, expiresAt || null);
|
|
551
|
+
return { id, title, advertiserName, status: 'pending' };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function getActiveAds(position) {
|
|
555
|
+
let q = `SELECT id, title, description, image_url, target_url, advertiser_name, position FROM wab_ads WHERE status = 'approved' AND (expires_at IS NULL OR expires_at > datetime('now')) AND (budget_cents <= 0 OR spent_cents < budget_cents)`;
|
|
556
|
+
const params = [];
|
|
557
|
+
if (position) { q += ` AND position = ?`; params.push(position); }
|
|
558
|
+
q += ` ORDER BY created_at DESC LIMIT 10`;
|
|
559
|
+
return db.prepare(q).all(...params);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function getAllAds() {
|
|
563
|
+
return db.prepare(`SELECT * FROM wab_ads ORDER BY created_at DESC`).all();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function getPendingAds() {
|
|
567
|
+
return db.prepare(`SELECT * FROM wab_ads WHERE status = 'pending' ORDER BY created_at ASC`).all();
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function getAdById(id) {
|
|
571
|
+
return db.prepare(`SELECT * FROM wab_ads WHERE id = ?`).get(id);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function updateAdStatus(id, status, adminId) {
|
|
575
|
+
const sets = ['status = ?'];
|
|
576
|
+
const params = [status];
|
|
577
|
+
if (status === 'approved') {
|
|
578
|
+
sets.push('approved_by = ?', 'approved_at = datetime(\'now\')');
|
|
579
|
+
params.push(adminId);
|
|
580
|
+
}
|
|
581
|
+
params.push(id);
|
|
582
|
+
db.prepare(`UPDATE wab_ads SET ${sets.join(', ')} WHERE id = ?`).run(...params);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function deleteAd(id) {
|
|
586
|
+
db.prepare(`DELETE FROM ad_events WHERE ad_id = ?`).run(id);
|
|
587
|
+
db.prepare(`DELETE FROM wab_ads WHERE id = ?`).run(id);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function recordAdEvent(adId, eventType, ipHash) {
|
|
591
|
+
// Deduplicate: skip if same ip+ad+event in last 60s
|
|
592
|
+
const recent = db.prepare(`SELECT 1 FROM ad_events WHERE ad_id = ? AND event_type = ? AND ip_hash = ? AND created_at > datetime('now', '-60 seconds') LIMIT 1`).get(adId, eventType, ipHash || '');
|
|
593
|
+
if (recent) return;
|
|
594
|
+
db.prepare(`INSERT INTO ad_events (ad_id, event_type, ip_hash) VALUES (?, ?, ?)`).run(adId, eventType, ipHash || null);
|
|
595
|
+
if (eventType === 'click') {
|
|
596
|
+
const ad = db.prepare(`SELECT cpc_cents FROM wab_ads WHERE id = ?`).get(adId);
|
|
597
|
+
if (ad) {
|
|
598
|
+
db.prepare(`UPDATE wab_ads SET clicks = clicks + 1, spent_cents = spent_cents + ? WHERE id = ?`).run(ad.cpc_cents, adId);
|
|
599
|
+
}
|
|
600
|
+
} else {
|
|
601
|
+
const ad = db.prepare(`SELECT cpi_cents FROM wab_ads WHERE id = ?`).get(adId);
|
|
602
|
+
if (ad) {
|
|
603
|
+
db.prepare(`UPDATE wab_ads SET impressions = impressions + 1, spent_cents = spent_cents + ? WHERE id = ?`).run(ad.cpi_cents, adId);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function getAdStats() {
|
|
609
|
+
const total = db.prepare(`SELECT COUNT(*) as c FROM wab_ads`).get().c;
|
|
610
|
+
const pending = db.prepare(`SELECT COUNT(*) as c FROM wab_ads WHERE status = 'pending'`).get().c;
|
|
611
|
+
const approved = db.prepare(`SELECT COUNT(*) as c FROM wab_ads WHERE status = 'approved'`).get().c;
|
|
612
|
+
const totalImpressions = db.prepare(`SELECT COALESCE(SUM(impressions), 0) as c FROM wab_ads`).get().c;
|
|
613
|
+
const totalClicks = db.prepare(`SELECT COALESCE(SUM(clicks), 0) as c FROM wab_ads`).get().c;
|
|
614
|
+
const totalRevenueCents = db.prepare(`SELECT COALESCE(SUM(spent_cents), 0) as c FROM wab_ads`).get().c;
|
|
615
|
+
return { total, pending, approved, totalImpressions, totalClicks, totalRevenueCents };
|
|
616
|
+
}
|
|
617
|
+
|
|
527
618
|
module.exports = {
|
|
528
619
|
db,
|
|
529
620
|
registerUser,
|
|
@@ -534,6 +625,7 @@ module.exports = {
|
|
|
534
625
|
findSitesByUser,
|
|
535
626
|
findSiteById,
|
|
536
627
|
findSiteByLicense,
|
|
628
|
+
findSiteByDomain,
|
|
537
629
|
updateSiteConfig,
|
|
538
630
|
updateSiteTier,
|
|
539
631
|
deleteSite,
|
|
@@ -546,7 +638,6 @@ module.exports = {
|
|
|
546
638
|
// Admin
|
|
547
639
|
createAdmin,
|
|
548
640
|
loginAdmin,
|
|
549
|
-
resetAdminPassword,
|
|
550
641
|
findAdminById,
|
|
551
642
|
maybeBootstrapAdmin,
|
|
552
643
|
getAllUsers,
|
|
@@ -576,5 +667,15 @@ module.exports = {
|
|
|
576
667
|
getNotificationLogs,
|
|
577
668
|
// Platform
|
|
578
669
|
getPlatformSetting,
|
|
579
|
-
setPlatformSetting
|
|
670
|
+
setPlatformSetting,
|
|
671
|
+
// Ads
|
|
672
|
+
submitAd,
|
|
673
|
+
getActiveAds,
|
|
674
|
+
getAllAds,
|
|
675
|
+
getPendingAds,
|
|
676
|
+
getAdById,
|
|
677
|
+
updateAdStatus,
|
|
678
|
+
deleteAd,
|
|
679
|
+
recordAdEvent,
|
|
680
|
+
getAdStats
|
|
580
681
|
};
|