openvibers/openvibe.inventory Commits
Sitemap: kind filters leave it (noindex, canonical /items); the Workshop is indexable, as the sitemap said (#58)
Changes
@@@ -65,12 +65,14 @@ async function catalog(inv) {}const kindsWithItems = ({ kinds, byKind }) => kinds.filter((k) => byKind.get(k.id).length);--/** Every public URL: the static pages, /items, a page per kind with items, and each published item. */--function catalogPages({ kinds, defs, byKind }) {++/**++ * Every indexable URL: the static pages, /items and each published item. A kind's filter (/items?kind=…) is not one:++ * the page says noindex with /items as its canonical, so the sitemap listing it spent crawl on a duplicate.++ */++function catalogPages({ defs }) {return [...publicPages(),CATALOG_PAGE,-- ...kindsWithItems({ kinds, byKind }).map((k) => ({ path: kindPath(k.id), changefreq: 'weekly', priority: 0.6 })),...defs.map((d) => ({ path: `/items/${d.id}`, changefreq: 'monthly', priority: 0.4 })),];}@@@ -63,7 +63,8 @@ function createWorkshopRoutes(ctx) {r.get('/workshop', async (req, res) => {const published = await inv.workshopItems({ status: 'published', limit: 200 });page(req, res, {-- title: 'The Workshop',++ // A public page in search, as the sitemap says (it was noindex by default).++ title: 'The Workshop', index: true,description: 'Badges made by people in the OpenVibe community, reviewed by staff, given free by their makers.',body: html`<h1>The Workshop</h1><p class="lede">Badges made by people in the community. Make one, staff review it, then you give it to the people you choose: your viewers, your server, the friends you played with. Every badge here is free: nothing in the Workshop is sold or traded.</p>@@@ -34,11 +34,12 @@ const PRIVATE = ['/auth/', '/api/'];assert.match(r.headers.get('content-type'), /^application\/xml/);assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);for (const p of ['/', '/updates']) assert.ok(r.text.includes(`<loc>https://inventory.openvibe.network${p}</loc>`), `no ${p} entry`);-- // The catalog: /items, each kind with items, and every published item.++ // The catalog: /items and every published item. A kind's filter is noindex with /items as canonical: not listed.assert.ok(r.text.includes('<loc>https://inventory.openvibe.network/items</loc>'), 'no /items entry');-- // This kind's URL has no '&', so it must appear exactly, unescaped — and with no stray '&'.-- assert.ok(r.text.includes('<loc>https://inventory.openvibe.network/items?kind=live.hat</loc>'), 'no /items?kind=live.hat entry');-- assert.ok(!r.text.includes('items?kind=live.hat&'), 'that URL must not gain an entity');++ assert.ok(!r.text.includes('items?kind='), 'a kind filter is no sitemap entry');++ assert.ok(r.text.includes('<loc>https://inventory.openvibe.network/workshop</loc>'), 'the Workshop is listed');++ const ws = await t.get('/workshop');++ assert.match(ws.text, /<meta name="robots" content="index, follow">/, 'and indexable');const items = [...r.text.matchAll(/<loc>https:\/\/inventory\.openvibe\.network\/items\/itd_/g)];assert.strictEqual(items.length, 70, `expected 70 item entries, got ${items.length}`);for (const p of ['/me', '/u/', '/api/']) assert.ok(!r.text.includes(`<loc>https://inventory.openvibe.network${p}`), `private path in sitemap: ${p}`);
@@ -65,12 +65,14 @@ async function catalog(inv) {
@@ -65,12 +65,14 @@ async function catalog(inv) {
}
}
const kindsWithItems = ({ kinds, byKind }) => kinds.filter((k) => byKind.get(k.id).length);
const kindsWithItems = ({ kinds, byKind }) => kinds.filter((k) => byKind.get(k.id).length);
-/** Every public URL: the static pages, /items, a page per kind with items, and each published item. */
-function catalogPages({ kinds, defs, byKind }) {
+/**
+ * Every indexable URL: the static pages, /items and each published item. A kind's filter (/items?kind=…) is not one:
+ * the page says noindex with /items as its canonical, so the sitemap listing it spent crawl on a duplicate.
+ */
+function catalogPages({ defs }) {
return [
return [
...publicPages(),
...publicPages(),
CATALOG_PAGE,
CATALOG_PAGE,
- ...kindsWithItems({ kinds, byKind }).map((k) => ({ path: kindPath(k.id), changefreq: 'weekly', priority: 0.6 })),
...defs.map((d) => ({ path: `/items/${d.id}`, changefreq: 'monthly', priority: 0.4 })),
...defs.map((d) => ({ path: `/items/${d.id}`, changefreq: 'monthly', priority: 0.4 })),
];
];
}
}
@@ -63,7 +63,8 @@ function createWorkshopRoutes(ctx) {
@@ -63,7 +63,8 @@ function createWorkshopRoutes(ctx) {
r.get('/workshop', async (req, res) => {
r.get('/workshop', async (req, res) => {
const published = await inv.workshopItems({ status: 'published', limit: 200 });
const published = await inv.workshopItems({ status: 'published', limit: 200 });
page(req, res, {
page(req, res, {
- title: 'The Workshop',
+ // A public page in search, as the sitemap says (it was noindex by default).
+ title: 'The Workshop', index: true,
description: 'Badges made by people in the OpenVibe community, reviewed by staff, given free by their makers.',
description: 'Badges made by people in the OpenVibe community, reviewed by staff, given free by their makers.',
body: html`<h1>The Workshop</h1>
body: html`<h1>The Workshop</h1>
<p class="lede">Badges made by people in the community. Make one, staff review it, then you give it to the people you choose: your viewers, your server, the friends you played with. Every badge here is free: nothing in the Workshop is sold or traded.</p>
<p class="lede">Badges made by people in the community. Make one, staff review it, then you give it to the people you choose: your viewers, your server, the friends you played with. Every badge here is free: nothing in the Workshop is sold or traded.</p>
@@ -34,11 +34,12 @@ const PRIVATE = ['/auth/', '/api/'];
@@ -34,11 +34,12 @@ const PRIVATE = ['/auth/', '/api/'];
assert.match(r.headers.get('content-type'), /^application\/xml/);
assert.match(r.headers.get('content-type'), /^application\/xml/);
assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);
assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);
for (const p of ['/', '/updates']) assert.ok(r.text.includes(`<loc>https://inventory.openvibe.network${p}</loc>`), `no ${p} entry`);
for (const p of ['/', '/updates']) assert.ok(r.text.includes(`<loc>https://inventory.openvibe.network${p}</loc>`), `no ${p} entry`);
- // The catalog: /items, each kind with items, and every published item.
+ // The catalog: /items and every published item. A kind's filter is noindex with /items as canonical: not listed.
assert.ok(r.text.includes('<loc>https://inventory.openvibe.network/items</loc>'), 'no /items entry');
assert.ok(r.text.includes('<loc>https://inventory.openvibe.network/items</loc>'), 'no /items entry');
- // This kind's URL has no '&', so it must appear exactly, unescaped — and with no stray '&'.
- assert.ok(r.text.includes('<loc>https://inventory.openvibe.network/items?kind=live.hat</loc>'), 'no /items?kind=live.hat entry');
- assert.ok(!r.text.includes('items?kind=live.hat&'), 'that URL must not gain an entity');
+ assert.ok(!r.text.includes('items?kind='), 'a kind filter is no sitemap entry');
+ assert.ok(r.text.includes('<loc>https://inventory.openvibe.network/workshop</loc>'), 'the Workshop is listed');
+ const ws = await t.get('/workshop');
+ assert.match(ws.text, /<meta name="robots" content="index, follow">/, 'and indexable');
const items = [...r.text.matchAll(/<loc>https:\/\/inventory\.openvibe\.network\/items\/itd_/g)];
const items = [...r.text.matchAll(/<loc>https:\/\/inventory\.openvibe\.network\/items\/itd_/g)];
assert.strictEqual(items.length, 70, `expected 70 item entries, got ${items.length}`);
assert.strictEqual(items.length, 70, `expected 70 item entries, got ${items.length}`);
for (const p of ['/me', '/u/', '/api/']) assert.ok(!r.text.includes(`<loc>https://inventory.openvibe.network${p}`), `private path in sitemap: ${p}`);
for (const p of ['/me', '/u/', '/api/']) assert.ok(!r.text.includes(`<loc>https://inventory.openvibe.network${p}`), `private path in sitemap: ${p}`);