web-agent-bridge 3.4.0 → 3.8.1
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/LICENSE +84 -84
- package/README.ar.md +1563 -1304
- package/README.md +137 -298
- package/bin/agent-runner.js +474 -474
- package/bin/cli.js +237 -237
- package/bin/wab-init.js +244 -223
- package/bin/wab.js +80 -80
- package/examples/azure-dns-wab.js +83 -83
- package/examples/bidi-agent.js +119 -119
- package/examples/cloudflare-wab-dns.js +121 -121
- package/examples/cpanel-wab-dns.js +114 -114
- package/examples/cross-site-agent.js +91 -91
- package/examples/dns-discovery-agent.js +166 -166
- package/examples/gcp-dns-wab.js +76 -76
- package/examples/governance-agent.js +169 -169
- package/examples/mcp-agent.js +94 -94
- package/examples/next-app-router/README.md +44 -44
- package/examples/plesk-wab-dns.js +103 -103
- package/examples/puppeteer-agent.js +108 -108
- package/examples/route53-wab-dns.js +144 -144
- package/examples/saas-dashboard/README.md +55 -55
- package/examples/safe-mode-agent.js +96 -96
- package/examples/self-discovery.js +106 -0
- package/examples/shopify-hydrogen/README.md +74 -74
- package/examples/vision-agent.js +171 -171
- package/examples/wab-sign.js +74 -74
- package/examples/wab-verify.js +60 -60
- package/examples/wordpress-elementor/README.md +77 -77
- package/package.json +93 -93
- package/public/.well-known/agent-tools.json +180 -180
- package/public/.well-known/ai-assets.json +59 -59
- package/public/.well-known/security.txt +8 -8
- package/public/.well-known/wab.json +28 -28
- package/public/activate.html +448 -368
- package/public/adopt.html +236 -0
- package/public/adoption-metrics.html +188 -188
- package/public/agent-workspace.html +359 -349
- package/public/ai.html +198 -198
- package/public/api.html +397 -413
- package/public/azure-dns-integration.html +289 -289
- package/public/browser.html +486 -486
- package/public/cloudflare-integration.html +380 -380
- package/public/commander-dashboard.html +243 -243
- package/public/cookies.html +210 -210
- package/public/cpanel-integration.html +398 -398
- package/public/css/agent-workspace.css +1713 -1713
- package/public/css/premium.css +317 -317
- package/public/css/styles.css +1401 -1263
- package/public/dashboard-shieldlink.html +295 -0
- package/public/dashboard.html +711 -707
- package/public/dns.html +436 -436
- package/public/docs.html +588 -588
- package/public/enterprise-mesh.ar.html +80 -0
- package/public/enterprise-mesh.html +81 -0
- package/public/feed.xml +89 -89
- package/public/gcp-dns-integration.html +318 -318
- package/public/governance.ar.html +70 -0
- package/public/governance.html +69 -0
- package/public/growth.html +465 -465
- package/public/index.html +1372 -1266
- package/public/integrations.html +556 -556
- package/public/js/activate.js +449 -145
- package/public/js/agent-workspace.js +1740 -1740
- package/public/js/auth-nav.js +117 -65
- package/public/js/auth-redirect.js +12 -12
- package/public/js/cookie-consent.js +56 -56
- package/public/js/dns.js +438 -438
- package/public/js/wab-demo-page.js +721 -721
- package/public/js/ws-client.js +74 -74
- package/public/l-preview.html +242 -0
- package/public/llms-full.txt +360 -360
- package/public/llms.txt +125 -125
- package/public/login.html +85 -85
- package/public/mesh-dashboard.html +328 -328
- package/public/milestones.html +346 -0
- package/public/one-click.html +779 -0
- package/public/openapi.json +669 -669
- package/public/partners.ar.html +145 -0
- package/public/partners.html +143 -0
- package/public/phone-shield.html +281 -281
- package/public/plesk-integration.html +375 -375
- package/public/premium-dashboard.html +2489 -2489
- package/public/premium.html +793 -793
- package/public/privacy.html +297 -297
- package/public/provider-onboarding.html +172 -172
- package/public/provider-sandbox.html +134 -134
- package/public/providers.html +359 -359
- package/public/refusals.html +172 -0
- package/public/register.html +105 -105
- package/public/registrar-integrations.html +141 -141
- package/public/ring4.html +292 -0
- package/public/robots.txt +99 -99
- package/public/route53-integration.html +531 -531
- package/public/score.html +263 -0
- package/public/script/wab-consent.d.ts +36 -36
- package/public/script/wab-consent.js +104 -104
- package/public/script/wab-schema.js +131 -131
- package/public/script/wab.d.ts +108 -108
- package/public/script/wab.min.js +580 -580
- package/public/security.txt +8 -8
- package/public/shieldlink.html +244 -0
- package/public/shieldqr.html +231 -231
- package/public/sitemap.xml +13 -1
- package/public/terms.html +256 -256
- package/public/trust-graph-api.ar.html +92 -0
- package/public/trust-graph-api.html +91 -0
- package/public/wab-features.html +560 -0
- package/public/wab-trust.html +200 -200
- package/public/wab-truth.html +375 -0
- package/public/wab-vs-protocols.html +210 -210
- package/public/whitepaper.html +449 -449
- package/script/ai-agent-bridge.js +1754 -1754
- package/sdk/README.md +99 -99
- package/sdk/agent-mesh.js +449 -449
- package/sdk/auto-discovery.js +301 -288
- package/sdk/commander.js +262 -262
- package/sdk/governance.js +262 -262
- package/sdk/index.d.ts +464 -464
- package/sdk/index.js +649 -649
- package/sdk/multi-agent.js +318 -318
- package/sdk/safe-mode.js +221 -221
- package/sdk/safety-shield.js +219 -219
- package/sdk/schema-discovery.js +83 -83
- package/server/adapters/index.js +520 -520
- package/server/config/plans.js +412 -367
- package/server/config/secrets.js +102 -102
- package/server/control-plane/index.js +301 -301
- package/server/data-plane/index.js +354 -354
- package/server/index.js +790 -670
- package/server/llm/index.js +404 -404
- package/server/middleware/adminAuth.js +35 -35
- package/server/middleware/api-tier.js +170 -0
- package/server/middleware/auth.js +50 -50
- package/server/middleware/featureGate.js +88 -88
- package/server/middleware/rateLimits.js +100 -100
- package/server/middleware/sensitiveAction.js +157 -157
- package/server/middleware/wab-trust.js +141 -0
- package/server/migrations/001_add_analytics_indexes.sql +7 -7
- package/server/migrations/002_premium_features.sql +418 -418
- package/server/migrations/003_ads_integer_cents.sql +33 -33
- package/server/migrations/004_agent_os.sql +158 -158
- package/server/migrations/005_marketplace_metering.sql +126 -126
- package/server/migrations/006_growth_suite.sql +138 -0
- package/server/migrations/007_governance.sql +106 -106
- package/server/migrations/008_plans.sql +144 -144
- package/server/migrations/009_shieldqr.sql +30 -30
- package/server/migrations/010_extended_trust.sql +33 -33
- package/server/migrations/011_outreach.sql +47 -0
- package/server/migrations/012_shieldlink.sql +116 -0
- package/server/migrations/013_ct_monitor.sql +13 -0
- package/server/migrations/014_wab_advanced_features.sql +128 -0
- package/server/migrations/015_wab_truth_layer.sql +101 -0
- package/server/migrations/016_ring4_external_trust.sql +84 -0
- package/server/migrations/017_ring4_extensions.sql +69 -0
- package/server/migrations/018_commercial_foundations.sql +167 -0
- package/server/migrations/019_unify_tier_constraints.sql +133 -0
- package/server/models/adapters/index.js +33 -33
- package/server/models/adapters/mysql.js +183 -183
- package/server/models/adapters/postgresql.js +172 -172
- package/server/models/adapters/sqlite.js +7 -7
- package/server/models/db.js +740 -740
- package/server/observability/failure-analysis.js +337 -337
- package/server/observability/index.js +394 -394
- package/server/protocol/capabilities.js +223 -223
- package/server/protocol/index.js +243 -243
- package/server/protocol/schema.js +584 -584
- package/server/registry/certification.js +271 -271
- package/server/registry/index.js +326 -326
- package/server/routes/activate.js +478 -0
- package/server/routes/admin-outreach.js +239 -0
- package/server/routes/admin-plans.js +76 -76
- package/server/routes/admin-premium.js +674 -673
- package/server/routes/admin-shieldlink.js +137 -0
- package/server/routes/admin-shieldqr.js +90 -90
- package/server/routes/admin-trust-monitor.js +139 -83
- package/server/routes/admin.js +550 -549
- package/server/routes/adopt.js +61 -0
- package/server/routes/ads.js +130 -130
- package/server/routes/agent-workspace.js +540 -540
- package/server/routes/api-keys.js +127 -0
- package/server/routes/api.js +150 -150
- package/server/routes/auth.js +71 -71
- package/server/routes/billing.js +57 -57
- package/server/routes/commander.js +316 -316
- package/server/routes/customer-shieldlink.js +133 -0
- package/server/routes/demo-showcase.js +332 -332
- package/server/routes/demo-store.js +154 -154
- package/server/routes/diagnose.js +373 -0
- package/server/routes/discovery.js +2348 -2348
- package/server/routes/enterprise-mesh.js +170 -0
- package/server/routes/gateway.js +173 -173
- package/server/routes/governance-saas.js +203 -0
- package/server/routes/governance.js +208 -208
- package/server/routes/growth.js +1048 -0
- package/server/routes/intent.js +328 -0
- package/server/routes/license.js +251 -251
- package/server/routes/mesh.js +469 -469
- package/server/routes/noscript.js +543 -543
- package/server/routes/partners.js +201 -0
- package/server/routes/plans.js +33 -33
- package/server/routes/premium-v2.js +686 -686
- package/server/routes/premium.js +724 -724
- package/server/routes/providers.js +650 -650
- package/server/routes/reputation.js +411 -0
- package/server/routes/ring4.js +885 -0
- package/server/routes/runtime.js +2148 -2148
- package/server/routes/shieldlink.js +70 -0
- package/server/routes/shieldqr.js +88 -88
- package/server/routes/sovereign.js +465 -465
- package/server/routes/truth-layer.js +670 -0
- package/server/routes/universal.js +200 -200
- package/server/routes/unsubscribe.js +51 -0
- package/server/routes/wab-api.js +850 -850
- package/server/routes/wab-cache.js +282 -0
- package/server/runtime/container-worker.js +111 -111
- package/server/runtime/container.js +448 -448
- package/server/runtime/distributed-worker.js +362 -362
- package/server/runtime/event-bus.js +210 -210
- package/server/runtime/index.js +253 -253
- package/server/runtime/queue.js +599 -599
- package/server/runtime/replay.js +666 -666
- package/server/runtime/sandbox.js +266 -266
- package/server/runtime/scheduler.js +534 -534
- package/server/runtime/session-engine.js +293 -293
- package/server/runtime/state-manager.js +188 -188
- package/server/secrets/wab-signing-key.pem +3 -0
- package/server/secrets/wab-signing-pub.pem +3 -0
- package/server/security/cross-site-redactor.js +196 -196
- package/server/security/dry-run.js +180 -180
- package/server/security/human-gate-rate-limit.js +147 -147
- package/server/security/human-gate-transports.js +178 -178
- package/server/security/human-gate.js +281 -281
- package/server/security/index.js +368 -368
- package/server/security/intent-engine.js +245 -245
- package/server/security/reward-guard.js +171 -171
- package/server/security/rollback-store.js +239 -239
- package/server/security/token-scope.js +404 -404
- package/server/security/url-policy.js +139 -139
- package/server/services/adoption-agent.js +182 -0
- package/server/services/agent-chat.js +506 -506
- package/server/services/agent-learning.js +601 -601
- package/server/services/agent-memory.js +625 -625
- package/server/services/agent-mesh.js +555 -555
- package/server/services/agent-symphony.js +717 -717
- package/server/services/agent-tasks.js +1807 -1807
- package/server/services/api-key-engine.js +292 -292
- package/server/services/cluster.js +894 -894
- package/server/services/commander.js +738 -738
- package/server/services/edge-compute.js +440 -440
- package/server/services/email.js +233 -233
- package/server/services/fairness-engine.js +409 -0
- package/server/services/fairness.js +420 -0
- package/server/services/governance.js +466 -466
- package/server/services/hosted-runtime.js +205 -205
- package/server/services/lfd.js +635 -635
- package/server/services/local-ai.js +389 -389
- package/server/services/marketplace.js +270 -270
- package/server/services/metering.js +182 -182
- package/server/services/modules/affiliate-intelligence.js +93 -93
- package/server/services/modules/agent-firewall.js +90 -90
- package/server/services/modules/bounty.js +89 -89
- package/server/services/modules/collective-bargaining.js +92 -92
- package/server/services/modules/dark-pattern.js +66 -66
- package/server/services/modules/gov-intelligence.js +45 -45
- package/server/services/modules/neural.js +55 -55
- package/server/services/modules/notary.js +49 -49
- package/server/services/modules/price-time-machine.js +86 -86
- package/server/services/modules/protocol.js +104 -104
- package/server/services/negotiation.js +439 -439
- package/server/services/outreach-agent.js +312 -0
- package/server/services/plans.js +214 -214
- package/server/services/plugins.js +771 -771
- package/server/services/price-intelligence.js +566 -566
- package/server/services/price-shield.js +1137 -1137
- package/server/services/provider-clients.js +740 -740
- package/server/services/reputation.js +465 -465
- package/server/services/search-engine.js +357 -357
- package/server/services/security.js +513 -513
- package/server/services/self-healing.js +843 -843
- package/server/services/shieldlink.js +492 -0
- package/server/services/shieldqr.js +322 -322
- package/server/services/sovereign-shield.js +542 -542
- package/server/services/ssl-ct-monitor.js +224 -0
- package/server/services/ssl-inspector.js +42 -42
- package/server/services/ssl-monitor.js +167 -167
- package/server/services/stripe.js +206 -205
- package/server/services/swarm.js +788 -788
- package/server/services/universal-scraper.js +662 -662
- package/server/services/verification.js +481 -481
- package/server/services/vision.js +1163 -1163
- package/server/services/wab-crypto.js +178 -178
- package/server/utils/cache.js +125 -125
- package/server/utils/migrate.js +81 -81
- package/server/utils/safe-fetch.js +228 -228
- package/server/utils/secureFields.js +50 -50
- package/server/ws.js +161 -161
- package/templates/artisan-marketplace.yaml +104 -104
- package/templates/book-price-scout.yaml +98 -98
- package/templates/electronics-price-tracker.yaml +108 -108
- package/templates/flight-deal-hunter.yaml +113 -113
- package/templates/freelancer-direct.yaml +116 -116
- package/templates/grocery-price-compare.yaml +93 -93
- package/templates/hotel-direct-booking.yaml +113 -113
- package/templates/local-services.yaml +98 -98
- package/templates/olive-oil-tunisia.yaml +88 -88
- package/templates/organic-farm-fresh.yaml +101 -101
- package/templates/restaurant-direct.yaml +97 -97
- package/templates/ring4/banking-sovereign.yaml +55 -0
- package/templates/ring4/ecommerce-sovereign.yaml +58 -0
- package/templates/ring4/healthcare-sovereign.yaml +60 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// WAB Certified Partner Program
|
|
3
|
+
//
|
|
4
|
+
// POST /api/partners/apply — self-serve application (any tier)
|
|
5
|
+
// GET /api/partners — public directory (approved partners)
|
|
6
|
+
// GET /api/partners/:partner_id — public partner profile (badge data)
|
|
7
|
+
// GET /api/partners/badge/:token — embeddable SVG badge
|
|
8
|
+
// GET /api/partners/admin/applications — admin list (token-gated)
|
|
9
|
+
// POST /api/partners/admin/approve — admin decision
|
|
10
|
+
//
|
|
11
|
+
// Tiers:
|
|
12
|
+
// basic — €0 — auto-approved if Ring4 status + DNS look healthy
|
|
13
|
+
// verified — €499 — manual review, requires handshake ≥ 9/9
|
|
14
|
+
// premium — €2.9k+/yr — manual review + DPA, sales motion
|
|
15
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const express = require('express');
|
|
20
|
+
const crypto = require('crypto');
|
|
21
|
+
const { db } = require('../models/db');
|
|
22
|
+
|
|
23
|
+
const router = express.Router();
|
|
24
|
+
|
|
25
|
+
const DOMAIN_RE = /^[a-z0-9.-]{3,253}$/i;
|
|
26
|
+
const SLUG_RE = /^[a-z0-9][a-z0-9-]{1,40}[a-z0-9]$/;
|
|
27
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
28
|
+
const TIER_SET = new Set(['basic', 'verified', 'premium']);
|
|
29
|
+
const CAT_SET = new Set(['bank','fintech','ecommerce','messaging','healthcare','government','media','saas','telecom','other']);
|
|
30
|
+
|
|
31
|
+
function slug(s) {
|
|
32
|
+
return String(s || '').toLowerCase()
|
|
33
|
+
.replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40);
|
|
34
|
+
}
|
|
35
|
+
function hashIp(ip) {
|
|
36
|
+
return crypto.createHash('sha256').update(String(ip || '') + (process.env.IP_HASH_SALT || 'wab-default-salt')).digest('hex').slice(0, 32);
|
|
37
|
+
}
|
|
38
|
+
function clip(s, n = 2000) { return typeof s === 'string' ? s.slice(0, n) : ''; }
|
|
39
|
+
|
|
40
|
+
function adminGate(req, res, next) {
|
|
41
|
+
const expected = process.env.WAB_PARTNERS_ADMIN_TOKEN || process.env.WAB_RING4_ADMIN_TOKEN;
|
|
42
|
+
if (!expected) return res.status(503).json({ error: 'admin_disabled' });
|
|
43
|
+
const presented = req.headers['x-admin-token'] || '';
|
|
44
|
+
if (presented !== expected) return res.status(401).json({ error: 'unauthorized' });
|
|
45
|
+
next();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
+
// POST /apply
|
|
50
|
+
router.post('/apply', (req, res) => {
|
|
51
|
+
const b = req.body || {};
|
|
52
|
+
const display_name = clip(b.display_name, 120);
|
|
53
|
+
const domain = String(b.domain || '').toLowerCase().trim();
|
|
54
|
+
const contact_email = String(b.contact_email || '').toLowerCase().trim();
|
|
55
|
+
const contact_name = clip(b.contact_name, 120);
|
|
56
|
+
const requested_tier = String(b.requested_tier || 'basic').toLowerCase();
|
|
57
|
+
const country = clip(b.country, 4);
|
|
58
|
+
const category = String(b.category || 'other').toLowerCase();
|
|
59
|
+
const website = clip(b.website, 200);
|
|
60
|
+
const use_case = clip(b.use_case, 1000);
|
|
61
|
+
const handshake_score = Math.max(0, Math.min(9, parseInt(b.handshake_score, 10) || 0));
|
|
62
|
+
|
|
63
|
+
if (!display_name) return res.status(400).json({ error: 'display_name required' });
|
|
64
|
+
if (!DOMAIN_RE.test(domain)) return res.status(400).json({ error: 'invalid domain' });
|
|
65
|
+
if (!EMAIL_RE.test(contact_email)) return res.status(400).json({ error: 'invalid contact_email' });
|
|
66
|
+
if (!TIER_SET.has(requested_tier)) return res.status(400).json({ error: 'invalid requested_tier' });
|
|
67
|
+
if (!CAT_SET.has(category)) return res.status(400).json({ error: 'invalid category' });
|
|
68
|
+
|
|
69
|
+
// Snapshot Ring 4 status if available
|
|
70
|
+
let ring4_status = null;
|
|
71
|
+
try {
|
|
72
|
+
const r = db.prepare(`SELECT trust_score, expires_at FROM ring4_trust_profiles WHERE domain = ?`).get(domain);
|
|
73
|
+
if (r) ring4_status = JSON.stringify(r);
|
|
74
|
+
} catch { /* table might not exist in some test envs */ }
|
|
75
|
+
|
|
76
|
+
const application_id = 'app_' + crypto.randomBytes(9).toString('base64url');
|
|
77
|
+
try {
|
|
78
|
+
db.prepare(`
|
|
79
|
+
INSERT INTO wab_partner_applications
|
|
80
|
+
(application_id, display_name, domain, requested_tier, contact_email, contact_name, country, category, website, use_case, ring4_status, handshake_score, ip_hash, user_agent)
|
|
81
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
82
|
+
`).run(application_id, display_name, domain, requested_tier, contact_email, contact_name, country, category, website, use_case, ring4_status, handshake_score, hashIp(req.ip), clip(req.headers['user-agent'], 200));
|
|
83
|
+
} catch (e) {
|
|
84
|
+
return res.status(500).json({ error: 'apply_failed', detail: e.message });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Auto-approval rule for Basic tier when handshake_score ≥ 8 AND Ring 4 status known.
|
|
88
|
+
let auto_approved = false;
|
|
89
|
+
let partner_id = null;
|
|
90
|
+
if (requested_tier === 'basic' && handshake_score >= 8 && ring4_status) {
|
|
91
|
+
partner_id = slug(display_name) || ('partner-' + crypto.randomBytes(3).toString('hex'));
|
|
92
|
+
const badge_token = crypto.randomBytes(18).toString('base64url');
|
|
93
|
+
try {
|
|
94
|
+
db.prepare(`
|
|
95
|
+
INSERT INTO wab_partners (partner_id, display_name, domain, tier, status, contact_email, country, category, website, badge_token, approved_at, approved_by)
|
|
96
|
+
VALUES (?, ?, ?, 'basic', 'active', ?, ?, ?, ?, ?, datetime('now'), 'auto')
|
|
97
|
+
ON CONFLICT(partner_id) DO UPDATE SET
|
|
98
|
+
display_name=excluded.display_name, domain=excluded.domain,
|
|
99
|
+
contact_email=excluded.contact_email, updated_at=datetime('now')
|
|
100
|
+
`).run(partner_id, display_name, domain, contact_email, country, category, website, badge_token);
|
|
101
|
+
db.prepare(`UPDATE wab_partner_applications SET status='approved', decided_at=datetime('now'), decided_by='auto' WHERE application_id = ?`).run(application_id);
|
|
102
|
+
auto_approved = true;
|
|
103
|
+
} catch (e) {
|
|
104
|
+
// Race / unique constraint — surface but keep application
|
|
105
|
+
return res.status(202).json({ ok: true, application_id, auto_approved: false, note: 'queued (slug collision)' });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return res.status(200).json({
|
|
110
|
+
ok: true,
|
|
111
|
+
application_id,
|
|
112
|
+
requested_tier,
|
|
113
|
+
auto_approved,
|
|
114
|
+
partner_id,
|
|
115
|
+
next_steps: auto_approved
|
|
116
|
+
? { badge_endpoint: `/api/partners/badge/${partner_id}.svg`, listing: `/api/partners/${partner_id}` }
|
|
117
|
+
: { eta_hours: requested_tier === 'verified' ? 72 : 168, contact: 'partners@webagentbridge.com' }
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
122
|
+
// GET / — public directory
|
|
123
|
+
router.get('/', (req, res) => {
|
|
124
|
+
const tier = req.query.tier && TIER_SET.has(String(req.query.tier)) ? String(req.query.tier) : null;
|
|
125
|
+
let rows;
|
|
126
|
+
try {
|
|
127
|
+
if (tier) {
|
|
128
|
+
rows = db.prepare(`SELECT partner_id, display_name, domain, tier, country, category, website, approved_at FROM wab_partners WHERE status='active' AND tier=? ORDER BY approved_at DESC`).all(tier);
|
|
129
|
+
} else {
|
|
130
|
+
rows = db.prepare(`SELECT partner_id, display_name, domain, tier, country, category, website, approved_at FROM wab_partners WHERE status='active' ORDER BY tier DESC, approved_at DESC`).all();
|
|
131
|
+
}
|
|
132
|
+
} catch (e) { return res.status(503).json({ error: 'directory_unavailable' }); }
|
|
133
|
+
res.json({ partners: rows, total: rows.length });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
137
|
+
// GET /:partner_id
|
|
138
|
+
router.get('/:partner_id([a-z0-9-]{3,42})', (req, res) => {
|
|
139
|
+
const row = db.prepare(`SELECT partner_id, display_name, domain, tier, status, country, category, website, approved_at FROM wab_partners WHERE partner_id = ? AND status='active'`).get(req.params.partner_id);
|
|
140
|
+
if (!row) return res.status(404).json({ error: 'partner_not_found' });
|
|
141
|
+
res.json(row);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
145
|
+
// GET /badge/:slug.svg — embeddable SVG (tier-coloured)
|
|
146
|
+
router.get('/badge/:slug([a-z0-9-]{3,42}\\.svg)', (req, res) => {
|
|
147
|
+
const partner_id = req.params.slug.replace(/\.svg$/, '');
|
|
148
|
+
const row = db.prepare(`SELECT tier, display_name FROM wab_partners WHERE partner_id = ? AND status='active'`).get(partner_id);
|
|
149
|
+
if (!row) return res.status(404).type('text/plain').send('not found');
|
|
150
|
+
const color = row.tier === 'premium' ? '#7c3aed' : row.tier === 'verified' ? '#0ea5e9' : '#10b981';
|
|
151
|
+
const label = row.tier === 'premium' ? 'WAB Certified Partner' : row.tier === 'verified' ? 'WAB Verified' : 'WAB Compatible';
|
|
152
|
+
const safe = String(row.display_name).replace(/[&<>"]/g, c => ({ '&':'&','<':'<','>':'>','"':'"' }[c]));
|
|
153
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="240" height="48" role="img" aria-label="${label}: ${safe}">
|
|
154
|
+
<rect width="240" height="48" rx="6" fill="${color}"/>
|
|
155
|
+
<text x="12" y="20" font-family="Inter,system-ui,sans-serif" font-size="11" fill="#fff" font-weight="700">${label}</text>
|
|
156
|
+
<text x="12" y="38" font-family="Inter,system-ui,sans-serif" font-size="13" fill="#fff">${safe}</text>
|
|
157
|
+
</svg>`;
|
|
158
|
+
res.set('Cache-Control', 'public, max-age=3600');
|
|
159
|
+
res.type('image/svg+xml').send(svg);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
163
|
+
// Admin — list pending applications
|
|
164
|
+
router.get('/admin/applications', adminGate, (req, res) => {
|
|
165
|
+
const status = String(req.query.status || 'pending');
|
|
166
|
+
if (!['pending','approved','rejected','withdrawn'].includes(status)) return res.status(400).json({ error: 'invalid status' });
|
|
167
|
+
const rows = db.prepare(`SELECT application_id, display_name, domain, requested_tier, contact_email, country, category, handshake_score, status, created_at FROM wab_partner_applications WHERE status = ? ORDER BY created_at DESC LIMIT 200`).all(status);
|
|
168
|
+
res.json({ applications: rows });
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Admin — approve / reject
|
|
172
|
+
router.post('/admin/approve', adminGate, (req, res) => {
|
|
173
|
+
const { application_id, decision, notes, override_tier } = req.body || {};
|
|
174
|
+
if (!application_id || !['approve','reject'].includes(decision)) return res.status(400).json({ error: 'application_id + decision required' });
|
|
175
|
+
const app = db.prepare(`SELECT * FROM wab_partner_applications WHERE application_id = ?`).get(application_id);
|
|
176
|
+
if (!app) return res.status(404).json({ error: 'application_not_found' });
|
|
177
|
+
if (app.status !== 'pending') return res.status(409).json({ error: 'already_' + app.status });
|
|
178
|
+
|
|
179
|
+
if (decision === 'reject') {
|
|
180
|
+
db.prepare(`UPDATE wab_partner_applications SET status='rejected', decision_notes=?, decided_at=datetime('now'), decided_by='admin' WHERE application_id=?`).run(clip(notes, 500), application_id);
|
|
181
|
+
return res.json({ ok: true, application_id, status: 'rejected' });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const tier = TIER_SET.has(String(override_tier || '').toLowerCase()) ? String(override_tier).toLowerCase() : app.requested_tier;
|
|
185
|
+
const partner_id = slug(app.display_name) || ('partner-' + crypto.randomBytes(3).toString('hex'));
|
|
186
|
+
const badge_token = crypto.randomBytes(18).toString('base64url');
|
|
187
|
+
try {
|
|
188
|
+
db.prepare(`
|
|
189
|
+
INSERT INTO wab_partners (partner_id, display_name, domain, tier, status, contact_email, country, category, website, badge_token, approved_at, approved_by, notes)
|
|
190
|
+
VALUES (?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, datetime('now'), 'admin', ?)
|
|
191
|
+
ON CONFLICT(partner_id) DO UPDATE SET
|
|
192
|
+
tier=excluded.tier, status='active', updated_at=datetime('now')
|
|
193
|
+
`).run(partner_id, app.display_name, app.domain, tier, app.contact_email, app.country, app.category, app.website, badge_token, clip(notes, 500));
|
|
194
|
+
db.prepare(`UPDATE wab_partner_applications SET status='approved', decision_notes=?, decided_at=datetime('now'), decided_by='admin' WHERE application_id=?`).run(clip(notes, 500), application_id);
|
|
195
|
+
} catch (e) {
|
|
196
|
+
return res.status(500).json({ error: 'approve_failed', detail: e.message });
|
|
197
|
+
}
|
|
198
|
+
res.json({ ok: true, application_id, partner_id, tier, status: 'approved' });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
module.exports = router;
|
package/server/routes/plans.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public Plans API — feeds the landing-page pricing section.
|
|
3
|
-
* No authentication; returns only public, non-archived plans.
|
|
4
|
-
*/
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
const express = require('express');
|
|
8
|
-
const router = express.Router();
|
|
9
|
-
const plansService = require('../services/plans');
|
|
10
|
-
|
|
11
|
-
router.get('/', (req, res) => {
|
|
12
|
-
try {
|
|
13
|
-
const plans = plansService.listPlans({ publicOnly: true });
|
|
14
|
-
const features = plansService.listFeatures();
|
|
15
|
-
res.json({
|
|
16
|
-
plans,
|
|
17
|
-
features,
|
|
18
|
-
generated_at: new Date().toISOString(),
|
|
19
|
-
});
|
|
20
|
-
} catch (err) {
|
|
21
|
-
res.status(500).json({ error: err.message });
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
router.get('/:id', (req, res) => {
|
|
26
|
-
const plan = plansService.getPlan(req.params.id);
|
|
27
|
-
if (!plan || plan.is_archived || !plan.is_public) {
|
|
28
|
-
return res.status(404).json({ error: 'plan not found' });
|
|
29
|
-
}
|
|
30
|
-
res.json({ plan });
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
module.exports = router;
|
|
1
|
+
/**
|
|
2
|
+
* Public Plans API — feeds the landing-page pricing section.
|
|
3
|
+
* No authentication; returns only public, non-archived plans.
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const express = require('express');
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
const plansService = require('../services/plans');
|
|
10
|
+
|
|
11
|
+
router.get('/', (req, res) => {
|
|
12
|
+
try {
|
|
13
|
+
const plans = plansService.listPlans({ publicOnly: true });
|
|
14
|
+
const features = plansService.listFeatures();
|
|
15
|
+
res.json({
|
|
16
|
+
plans,
|
|
17
|
+
features,
|
|
18
|
+
generated_at: new Date().toISOString(),
|
|
19
|
+
});
|
|
20
|
+
} catch (err) {
|
|
21
|
+
res.status(500).json({ error: err.message });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
router.get('/:id', (req, res) => {
|
|
26
|
+
const plan = plansService.getPlan(req.params.id);
|
|
27
|
+
if (!plan || plan.is_archived || !plan.is_public) {
|
|
28
|
+
return res.status(404).json({ error: 'plan not found' });
|
|
29
|
+
}
|
|
30
|
+
res.json({ plan });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
module.exports = router;
|