openvibers/openvibe.exchange Commits
Rankings is Exchange's own page, and the legal pages keep the same list (#231)
Changes
@@@ -187,7 +187,15 @@ async function createApp(opts = {}) {// ── Sign-in (OAuth2 + PKCE client of OpenVibe.Network) ──app.use('/auth/', rateLimit({ windowMs: 15 * 60_000, limit: 60, skip: (req) => req.path === '/me' || req.path === '/navbar-token', standardHeaders: true, legacyHeaders: false }));app.use('/auth', sso.routes());-- { const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'exchange', service: 'exchange', host: 'openvibe.exchange', name: 'OpenVibe.Exchange', profile: 'ugc' })); }++ { const legal = require('openvibe-shared/legal');++ // Shared's legal template starts the navbar without links, so /terms and /privacy showed its fallback++ // (Home, My Account, Themes) instead of Exchange's own pages. Add Exchange's list to that init call.++ const navLinks = JSON.stringify(require('./render/layout').NAV);++ app.get(legal.PATHS, (req, res, next) => {++ const send = res.send.bind(res);++ res.send = (body) => send(String(body).replace('try{OpenVibeNavbar.init({service:', `try{OpenVibeNavbar.init({links:${navLinks},service:`));++ next();++ }, legal.handler({ id: 'exchange', service: 'exchange', host: 'openvibe.exchange', name: 'OpenVibe.Exchange', profile: 'ugc' })); }// ── OpenVibe.Events → this service (loopback only) ──────// Account export/deletion; the consumer reads the raw body itself, so it is mounted before any body parser.@@@ -27,7 +27,7 @@ const NAV = [{ label: 'Pricing', href: '/pricing', icon: 'fa-tag' },{ label: 'Docs', href: '/docs', icon: 'fa-code' },{ label: 'Status', href: '/status', icon: 'fa-gauge' },-- { label: 'Rankings', href: 'https://openvibe.report/ai', icon: 'fa-chart-line', more: true },++ { label: 'Rankings', href: '/rankings', icon: 'fa-chart-line', more: true },];const hashes = new Map();@@@ -10,7 +10,7 @@ assert.ok(inBar.length <= 6, `at most six places in the bar, not ${inBar.length}assert.ok(NAV.every((l) => /^fa-[a-z0-9-]+$/.test(l.icon || '')), 'every place has an icon');assert.ok(!NAV.some((l) => l.href === '/'), 'the brand is the way home');assert.deepStrictEqual(new Set(NAV.map((l) => l.href)).size, NAV.length, 'no place twice');--for (const want of ['/models', '/search', '/pricing', '/docs', '/status', 'https://openvibe.report/ai']) {++for (const want of ['/models', '/search', '/pricing', '/docs', '/status', '/rankings']) {assert.ok(NAV.some((l) => l.href === want), `${want} is reachable from the bar or More`);}assert.ok(!NAV.some(l => ['/chat','/arena','/sell','/ai'].includes(l.href)), 'moved products leave the navigation');nnew file mode 100644@@@ -0,0 +1,50 @@++'use strict';++// Every page of openvibe.exchange gives the shared navbar the site's own page list (the owner, 2026-11-12): Rankings++// was the one row pointing at another site (Report); Exchange has had its own /rankings page all along.++const assert = require('assert');++const { boot, check, done } = require('./helpers/boot');++const { createFakeAi } = require('./helpers/ai');++const { NAV } = require('../server/render/layout');++++function initLinks(html) {++ const at = html.indexOf('OpenVibeNavbar.init');++ if (at < 0) return null;++ const start = html.indexOf('links', at);++ const open = html.indexOf('[', start);++ let depth = 0, q = null, esc = false;++ for (let i = open; i < html.length; i += 1) {++ const c = html[i];++ if (q) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === q) q = null; continue; }++ if (c === '"' || c === "'") { q = c; continue; }++ if (c === '[') depth += 1; else if (c === ']') { depth -= 1; if (!depth) return JSON.parse(html.slice(open, i + 1)); }++ }++ return null;++}++++(async () => {++ const t = await boot({ ai: createFakeAi() });++ try {++ const own = NAV.map(({ label, href }) => ({ label, href }));++ await check('every page sends Exchange\'s own list, and every row is on this host', async () => {++ for (const path of ['/', '/terms', '/privacy', '/dmca', ...NAV.map((l) => l.href)]) {++ const r = await t.get(path);++ assert.ok([200, 301, 302].includes(r.status), `${path} answered ${r.status}`);++ if (r.status !== 200) continue;++ const links = (initLinks(r.text) || []).map(({ label, href }) => ({ label, href }));++ assert.deepStrictEqual(links, own, `${path} sends Exchange's list`);++ }++ });++ await check('every row is a route of this server', async () => {++ for (const { href } of NAV) {++ const r = await t.get(href);++ assert.notStrictEqual(r.status, 404, `${href} is a route`);++ assert.ok(r.status < 500, `${href} does not fail (${r.status})`);++ }++ });++ await check('Rankings is this site\'s page, not a link to another site', async () => {++ const rankings = NAV.find((l) => l.label === 'Rankings');++ assert.strictEqual(rankings.href, '/rankings');++ });++ } finally { await t.close(); }++ done();++})().catch((e) => { console.error(e); process.exit(1); });
@@ -187,7 +187,15 @@ async function createApp(opts = {}) {
@@ -187,7 +187,15 @@ async function createApp(opts = {}) {
// ── Sign-in (OAuth2 + PKCE client of OpenVibe.Network) ──
// ── Sign-in (OAuth2 + PKCE client of OpenVibe.Network) ──
app.use('/auth/', rateLimit({ windowMs: 15 * 60_000, limit: 60, skip: (req) => req.path === '/me' || req.path === '/navbar-token', standardHeaders: true, legacyHeaders: false }));
app.use('/auth/', rateLimit({ windowMs: 15 * 60_000, limit: 60, skip: (req) => req.path === '/me' || req.path === '/navbar-token', standardHeaders: true, legacyHeaders: false }));
app.use('/auth', sso.routes());
app.use('/auth', sso.routes());
- { const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'exchange', service: 'exchange', host: 'openvibe.exchange', name: 'OpenVibe.Exchange', profile: 'ugc' })); }
+ { const legal = require('openvibe-shared/legal');
+ // Shared's legal template starts the navbar without links, so /terms and /privacy showed its fallback
+ // (Home, My Account, Themes) instead of Exchange's own pages. Add Exchange's list to that init call.
+ const navLinks = JSON.stringify(require('./render/layout').NAV);
+ app.get(legal.PATHS, (req, res, next) => {
+ const send = res.send.bind(res);
+ res.send = (body) => send(String(body).replace('try{OpenVibeNavbar.init({service:', `try{OpenVibeNavbar.init({links:${navLinks},service:`));
+ next();
+ }, legal.handler({ id: 'exchange', service: 'exchange', host: 'openvibe.exchange', name: 'OpenVibe.Exchange', profile: 'ugc' })); }
// ── OpenVibe.Events → this service (loopback only) ──────
// ── OpenVibe.Events → this service (loopback only) ──────
// Account export/deletion; the consumer reads the raw body itself, so it is mounted before any body parser.
// Account export/deletion; the consumer reads the raw body itself, so it is mounted before any body parser.
@@ -27,7 +27,7 @@ const NAV = [
@@ -27,7 +27,7 @@ const NAV = [
{ label: 'Pricing', href: '/pricing', icon: 'fa-tag' },
{ label: 'Pricing', href: '/pricing', icon: 'fa-tag' },
{ label: 'Docs', href: '/docs', icon: 'fa-code' },
{ label: 'Docs', href: '/docs', icon: 'fa-code' },
{ label: 'Status', href: '/status', icon: 'fa-gauge' },
{ label: 'Status', href: '/status', icon: 'fa-gauge' },
- { label: 'Rankings', href: 'https://openvibe.report/ai', icon: 'fa-chart-line', more: true },
+ { label: 'Rankings', href: '/rankings', icon: 'fa-chart-line', more: true },
];
];
const hashes = new Map();
const hashes = new Map();
@@ -10,7 +10,7 @@ assert.ok(inBar.length <= 6, `at most six places in the bar, not ${inBar.length}
@@ -10,7 +10,7 @@ assert.ok(inBar.length <= 6, `at most six places in the bar, not ${inBar.length}
assert.ok(NAV.every((l) => /^fa-[a-z0-9-]+$/.test(l.icon || '')), 'every place has an icon');
assert.ok(NAV.every((l) => /^fa-[a-z0-9-]+$/.test(l.icon || '')), 'every place has an icon');
assert.ok(!NAV.some((l) => l.href === '/'), 'the brand is the way home');
assert.ok(!NAV.some((l) => l.href === '/'), 'the brand is the way home');
assert.deepStrictEqual(new Set(NAV.map((l) => l.href)).size, NAV.length, 'no place twice');
assert.deepStrictEqual(new Set(NAV.map((l) => l.href)).size, NAV.length, 'no place twice');
-for (const want of ['/models', '/search', '/pricing', '/docs', '/status', 'https://openvibe.report/ai']) {
+for (const want of ['/models', '/search', '/pricing', '/docs', '/status', '/rankings']) {
assert.ok(NAV.some((l) => l.href === want), `${want} is reachable from the bar or More`);
assert.ok(NAV.some((l) => l.href === want), `${want} is reachable from the bar or More`);
}
}
assert.ok(!NAV.some(l => ['/chat','/arena','/sell','/ai'].includes(l.href)), 'moved products leave the navigation');
assert.ok(!NAV.some(l => ['/chat','/arena','/sell','/ai'].includes(l.href)), 'moved products leave the navigation');
new file mode 100644
new file mode 100644
@@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
+'use strict';
+// Every page of openvibe.exchange gives the shared navbar the site's own page list (the owner, 2026-11-12): Rankings
+// was the one row pointing at another site (Report); Exchange has had its own /rankings page all along.
+const assert = require('assert');
+const { boot, check, done } = require('./helpers/boot');
+const { createFakeAi } = require('./helpers/ai');
+const { NAV } = require('../server/render/layout');
+
+function initLinks(html) {
+ const at = html.indexOf('OpenVibeNavbar.init');
+ if (at < 0) return null;
+ const start = html.indexOf('links', at);
+ const open = html.indexOf('[', start);
+ let depth = 0, q = null, esc = false;
+ for (let i = open; i < html.length; i += 1) {
+ const c = html[i];
+ if (q) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === q) q = null; continue; }
+ if (c === '"' || c === "'") { q = c; continue; }
+ if (c === '[') depth += 1; else if (c === ']') { depth -= 1; if (!depth) return JSON.parse(html.slice(open, i + 1)); }
+ }
+ return null;
+}
+
+(async () => {
+ const t = await boot({ ai: createFakeAi() });
+ try {
+ const own = NAV.map(({ label, href }) => ({ label, href }));
+ await check('every page sends Exchange\'s own list, and every row is on this host', async () => {
+ for (const path of ['/', '/terms', '/privacy', '/dmca', ...NAV.map((l) => l.href)]) {
+ const r = await t.get(path);
+ assert.ok([200, 301, 302].includes(r.status), `${path} answered ${r.status}`);
+ if (r.status !== 200) continue;
+ const links = (initLinks(r.text) || []).map(({ label, href }) => ({ label, href }));
+ assert.deepStrictEqual(links, own, `${path} sends Exchange's list`);
+ }
+ });
+ await check('every row is a route of this server', async () => {
+ for (const { href } of NAV) {
+ const r = await t.get(href);
+ assert.notStrictEqual(r.status, 404, `${href} is a route`);
+ assert.ok(r.status < 500, `${href} does not fail (${r.status})`);
+ }
+ });
+ await check('Rankings is this site\'s page, not a link to another site', async () => {
+ const rankings = NAV.find((l) => l.label === 'Rankings');
+ assert.strictEqual(rankings.href, '/rankings');
+ });
+ } finally { await t.close(); }
+ done();
+})().catch((e) => { console.error(e); process.exit(1); });