openvibers/openvibe.quest Commits
A badge says which site's quest awards it (#173)
Changes
@@@ -145,13 +145,16 @@ const completionWire = (c) => ({coins: { amount: Number(c.coins), state: c.coins_state, error: c.coins_error || null, at: c.coins_at || null },});--/** A badge as anyone may see it: the id, its catalog name, and nothing about the person. */++/**++ * A badge as anyone may see it: the id, its catalog name, and the site whose quest awards it (a profile shows that site's++ * icon on the badge), and nothing about the person.++ */let appBadgeName = () => null;/** server/app.js points this at the app quests' badge names. */const setAppBadgeNames = (fn) => { appBadgeName = typeof fn === 'function' ? fn : () => null; };const badgeWire = (badgeId) => {const badge = catalog.badgeById(badgeId);-- return { id: badgeId, name: badge ? badge.name : (appBadgeName(badgeId) || badgeId) };++ return { id: badgeId, name: badge ? badge.name : (appBadgeName(badgeId) || badgeId), ...(badge && badge.site ? { site: badge.site } : {}) };};module.exports = { createApi, questWire, completionWire, badgeWire, setAppBadgeNames, SUBJECT_RE };@@@ -518,7 +518,7 @@ const seriesById = (id) => {};/** The badge for a quest, by badge id (the public badge endpoint stores only the id). */const badgeById = (badgeId) => {-- for (const q of QUESTS) if (q.reward.badge.id === badgeId) return { ...q.reward.badge, quest_id: q.id };++ for (const q of QUESTS) if (q.reward.badge.id === badgeId) return { ...q.reward.badge, quest_id: q.id, site: q.site };return null;};@@@ -99,8 +99,9 @@ const { render } = require('../server/render/html');assert.strictEqual(anon.status, 200, 'no token needed');const body = anon.json();assert.strictEqual(body.subject, kim.subject);-- assert.deepStrictEqual(body.badges, [{ id: 'first-broadcast', name: 'First Broadcast', awarded_at: '2026-10-01T10:00:00.000Z' }]);-- for (const b of body.badges) assert.deepStrictEqual(Object.keys(b).sort(), ['awarded_at', 'id', 'name'], 'nothing else about the person');++ assert.deepStrictEqual(body.badges, [{ id: 'first-broadcast', name: 'First Broadcast', site: 'openvibe.live', awarded_at: '2026-10-01T10:00:00.000Z' }]);++ // The site is the badge's (the site whose quest awards it), not the person's.++ for (const b of body.badges) assert.deepStrictEqual(Object.keys(b).sort(), ['awarded_at', 'id', 'name', 'site'], 'nothing else about the person');assert.ok(!anon.text.includes('qcp_'), 'no completion');assert.ok(!anon.text.includes(kim.username), 'no username');assert.ok(!anon.text.includes('coins'), 'no coins');
@@ -145,13 +145,16 @@ const completionWire = (c) => ({
@@ -145,13 +145,16 @@ const completionWire = (c) => ({
coins: { amount: Number(c.coins), state: c.coins_state, error: c.coins_error || null, at: c.coins_at || null },
coins: { amount: Number(c.coins), state: c.coins_state, error: c.coins_error || null, at: c.coins_at || null },
});
});
-/** A badge as anyone may see it: the id, its catalog name, and nothing about the person. */
+/**
+ * A badge as anyone may see it: the id, its catalog name, and the site whose quest awards it (a profile shows that site's
+ * icon on the badge), and nothing about the person.
+ */
let appBadgeName = () => null;
let appBadgeName = () => null;
/** server/app.js points this at the app quests' badge names. */
/** server/app.js points this at the app quests' badge names. */
const setAppBadgeNames = (fn) => { appBadgeName = typeof fn === 'function' ? fn : () => null; };
const setAppBadgeNames = (fn) => { appBadgeName = typeof fn === 'function' ? fn : () => null; };
const badgeWire = (badgeId) => {
const badgeWire = (badgeId) => {
const badge = catalog.badgeById(badgeId);
const badge = catalog.badgeById(badgeId);
- return { id: badgeId, name: badge ? badge.name : (appBadgeName(badgeId) || badgeId) };
+ return { id: badgeId, name: badge ? badge.name : (appBadgeName(badgeId) || badgeId), ...(badge && badge.site ? { site: badge.site } : {}) };
};
};
module.exports = { createApi, questWire, completionWire, badgeWire, setAppBadgeNames, SUBJECT_RE };
module.exports = { createApi, questWire, completionWire, badgeWire, setAppBadgeNames, SUBJECT_RE };
@@ -518,7 +518,7 @@ const seriesById = (id) => {
@@ -518,7 +518,7 @@ const seriesById = (id) => {
};
};
/** The badge for a quest, by badge id (the public badge endpoint stores only the id). */
/** The badge for a quest, by badge id (the public badge endpoint stores only the id). */
const badgeById = (badgeId) => {
const badgeById = (badgeId) => {
- for (const q of QUESTS) if (q.reward.badge.id === badgeId) return { ...q.reward.badge, quest_id: q.id };
+ for (const q of QUESTS) if (q.reward.badge.id === badgeId) return { ...q.reward.badge, quest_id: q.id, site: q.site };
return null;
return null;
};
};
@@ -99,8 +99,9 @@ const { render } = require('../server/render/html');
@@ -99,8 +99,9 @@ const { render } = require('../server/render/html');
assert.strictEqual(anon.status, 200, 'no token needed');
assert.strictEqual(anon.status, 200, 'no token needed');
const body = anon.json();
const body = anon.json();
assert.strictEqual(body.subject, kim.subject);
assert.strictEqual(body.subject, kim.subject);
- assert.deepStrictEqual(body.badges, [{ id: 'first-broadcast', name: 'First Broadcast', awarded_at: '2026-10-01T10:00:00.000Z' }]);
- for (const b of body.badges) assert.deepStrictEqual(Object.keys(b).sort(), ['awarded_at', 'id', 'name'], 'nothing else about the person');
+ assert.deepStrictEqual(body.badges, [{ id: 'first-broadcast', name: 'First Broadcast', site: 'openvibe.live', awarded_at: '2026-10-01T10:00:00.000Z' }]);
+ // The site is the badge's (the site whose quest awards it), not the person's.
+ for (const b of body.badges) assert.deepStrictEqual(Object.keys(b).sort(), ['awarded_at', 'id', 'name', 'site'], 'nothing else about the person');
assert.ok(!anon.text.includes('qcp_'), 'no completion');
assert.ok(!anon.text.includes('qcp_'), 'no completion');
assert.ok(!anon.text.includes(kim.username), 'no username');
assert.ok(!anon.text.includes(kim.username), 'no username');
assert.ok(!anon.text.includes('coins'), 'no coins');
assert.ok(!anon.text.includes('coins'), 'no coins');