@bongos/core 1.23.14 → 1.23.15
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/.bongos-core.json +77 -42
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +2 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +22 -1
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +924 -878
- package/docs/copy-registry.json +1796 -1325
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +8 -3
- package/docs/page-readings.json +575 -555
- package/modules/platform-identity/builder-of-the-week.js +180 -0
- package/modules/platform-identity/migrations/platform_identity_032_weekly_features.sql +53 -0
- package/modules/platform-identity/routes/community-leaderboard.js +25 -1
- package/modules/public-landing/public/account.html +2 -1
- package/modules/public-landing/public/community-week-none.states.json +16 -0
- package/modules/public-landing/public/community.html +142 -13
- package/modules/public-landing/public/community.states.json +6 -4
- package/modules/public-landing/public/connections.html +2 -1
- package/modules/public-landing/public/contact.html +2 -1
- package/modules/public-landing/public/guild.html +2 -1
- package/modules/public-landing/public/index.html +2 -1
- package/modules/public-landing/public/invitations.html +2 -1
- package/modules/public-landing/public/privacy.html +2 -1
- package/modules/public-landing/public/projects.html +2 -1
- package/modules/public-landing/public/terms.html +2 -1
- package/modules/ui-design/kit/fixtures/community-week-none.json +6 -0
- package/modules/ui-design/kit/fixtures/community-week.json +11 -0
- package/modules/ui-design/kit/serve.js +8 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/bongos/run-unit-tests.js +4 -0
- package/src/module-api.js +1 -1
- package/src/platform-server.js +5 -5
- package/tests/builder_of_the_week.mjs +266 -0
- package/tests/builder_of_the_week_db.mjs +166 -0
- package/tests/hub_account_page.mjs +2 -2
- package/tests/hub_community_page.mjs +136 -25
- package/tests/landing_legal_pages.mjs +2 -2
- package/tests/privacy_matrix.mjs +53 -8
package/tests/privacy_matrix.mjs
CHANGED
|
@@ -150,6 +150,10 @@ function identitySentinels(acct) {
|
|
|
150
150
|
// 'listed' | 'name-only' (listed, disciplines null) | 'absent'
|
|
151
151
|
// board GET /community/leaderboard Anon · Builder — the cross-project leaderboard
|
|
152
152
|
// 'listed' | 'absent'
|
|
153
|
+
// week GET /community/builder-of-the-week Anon · Builder · Recruiter — the crown and
|
|
154
|
+
// the past weeks (task 1002981, spec D5): a row IS stats, so the board's rule.
|
|
155
|
+
// Every fixture account has won a recorded week, so the read's own predicate
|
|
156
|
+
// decides: 'listed' | 'absent'
|
|
153
157
|
// directory GET /scouting Recruiter — 'full' | 'name-only' | 'absent'
|
|
154
158
|
// sliver GET /scouting/:handle Recruiter — 'full' (D3) | 'name-only' (D3, hidden) | 404
|
|
155
159
|
// self GET /me/cross-project Self — 'own' (own numbers, any flags) | 'n/a' (no session exists)
|
|
@@ -166,13 +170,13 @@ function identitySentinels(acct) {
|
|
|
166
170
|
// people the guild page already shows them — shown_publicly AND existence;
|
|
167
171
|
// hide_stats never hides membership, and private or provisional is never listed
|
|
168
172
|
const MATRIX = {
|
|
169
|
-
PROV: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 404, self: 'n/a', guild: 'absent', members: 'absent', invite: 'absent' },
|
|
170
|
-
PUB: { profile: 'full', search: 'listed', board: 'listed', directory: 'full', sliver: 'full', self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
|
|
171
|
-
SHY: { profile: 'identity', search: 'name-only', board: 'absent', directory: 'name-only', sliver: 'name-only', self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
|
|
172
|
-
PRIV: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 404, self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
|
|
173
|
-
PRIVD: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 'full', self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
|
|
174
|
-
PRIVDH: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 'name-only', self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
|
|
175
|
-
OPTOUT: { profile: 'full', search: 'listed', board: 'listed', directory: 'absent', sliver: 404, self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
|
|
173
|
+
PROV: { profile: 404, search: 'absent', board: 'absent', week: 'absent', directory: 'absent', sliver: 404, self: 'n/a', guild: 'absent', members: 'absent', invite: 'absent' },
|
|
174
|
+
PUB: { profile: 'full', search: 'listed', board: 'listed', week: 'listed', directory: 'full', sliver: 'full', self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
|
|
175
|
+
SHY: { profile: 'identity', search: 'name-only', board: 'absent', week: 'absent', directory: 'name-only', sliver: 'name-only', self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
|
|
176
|
+
PRIV: { profile: 404, search: 'absent', board: 'absent', week: 'absent', directory: 'absent', sliver: 404, self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
|
|
177
|
+
PRIVD: { profile: 404, search: 'absent', board: 'absent', week: 'absent', directory: 'absent', sliver: 'full', self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
|
|
178
|
+
PRIVDH: { profile: 404, search: 'absent', board: 'absent', week: 'absent', directory: 'absent', sliver: 'name-only', self: 'own', guild: 'absent', members: 'listed', invite: 'absent' },
|
|
179
|
+
OPTOUT: { profile: 'full', search: 'listed', board: 'listed', week: 'listed', directory: 'absent', sliver: 404, self: 'own', guild: 'listed', members: 'listed', invite: 'listed' },
|
|
176
180
|
};
|
|
177
181
|
|
|
178
182
|
// ===========================================================================
|
|
@@ -280,6 +284,19 @@ function guildRosterFilter(sql, params) {
|
|
|
280
284
|
// origin the hall links point at.
|
|
281
285
|
const MX_CLIENT = { client_id: 'mx-proj', secret_hash: '', name: 'MX Project', redirect_uris: [], origin: 'https://mx-proj.example', owner_github_id: null, status: 'active' };
|
|
282
286
|
|
|
287
|
+
// THE WEEKS each fixture account won (task 1002981): PUB the week now crowned, every
|
|
288
|
+
// other account a week before it, so the crown and the past list both carry a cell.
|
|
289
|
+
// Lazy: the module may only load after the pool is pinned below, through the routes.
|
|
290
|
+
let weeksWon = null;
|
|
291
|
+
function weekOf(key) {
|
|
292
|
+
if (!weeksWon) {
|
|
293
|
+
const { crownedWeek } = require('../modules/platform-identity/builder-of-the-week.js');
|
|
294
|
+
weeksWon = Object.fromEntries(['PUB', ...KEYS.filter((k) => k !== 'PUB')].map((k, i) =>
|
|
295
|
+
[k, new Date(crownedWeek().start.getTime() - i * 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10)]));
|
|
296
|
+
}
|
|
297
|
+
return weeksWon[key];
|
|
298
|
+
}
|
|
299
|
+
|
|
283
300
|
const pool = {
|
|
284
301
|
query: async (text, params = []) => {
|
|
285
302
|
const sql = String(text).replace(/\s+/g, ' ').trim();
|
|
@@ -338,6 +355,21 @@ const pool = {
|
|
|
338
355
|
// ... and the weekly totals: never computed on a read (guild-totals.js); none stored here.
|
|
339
356
|
if (/FROM platform_identity_guild_totals WHERE guild_id = \$1$/.test(sql)) return { rows: [], rowCount: 0 };
|
|
340
357
|
|
|
358
|
+
// BUILDER OF THE WEEK (task 1002981): every week is already recorded (no crowning
|
|
359
|
+
// here), one per fixture account, PUB the current one — so the read's own predicate
|
|
360
|
+
// decides who is shown. The projection is pinned: a new column is a new disclosure.
|
|
361
|
+
if (/^SELECT 1 FROM platform_identity_weekly_features WHERE week_start = \$1::date$/.test(sql)) return { rows: [{ '?column?': 1 }], rowCount: 1 };
|
|
362
|
+
if (/FROM platform_identity_weekly_features f JOIN platform_identity_accounts pa/.test(sql)) {
|
|
363
|
+
const cols = (/^SELECT (.+?) FROM /.exec(sql) || [, ''])[1];
|
|
364
|
+
if (cols !== "to_char(f.week_start, 'YYYY-MM-DD') AS week_start, f.credits_delta, f.ships_delta, pa.handle, pa.github_login, pa.display_name, pa.avatar_url") throw new Error(`fake pool: the builder-of-the-week read selects something new — ${cols}`);
|
|
365
|
+
const where = lift(sql, /WHERE f\.week_start <= \$1::date AND (.+?) ORDER BY/, 'builder of the week');
|
|
366
|
+
const rows = ACCOUNTS.filter(accountFilter(where, [])).map((a) => ({
|
|
367
|
+
week_start: weekOf(a.key), credits_delta: String(ROLLUP[a.github_id].credits), ships_delta: String(ROLLUP[a.github_id].works_shipped),
|
|
368
|
+
handle: a.handle, github_login: a.github_login, display_name: a.display_name, avatar_url: a.avatar_url,
|
|
369
|
+
})).sort((x, y) => (x.week_start < y.week_start ? 1 : -1)).slice(0, params[1]);
|
|
370
|
+
return { rows, rowCount: rows.length };
|
|
371
|
+
}
|
|
372
|
+
|
|
341
373
|
// THE LEADERBOARD's totals: the rollup floor sits on the act CTE's join.
|
|
342
374
|
if (/platform_identity_activity_snapshots/.test(sql)) {
|
|
343
375
|
const where = lift(sql, /JOIN platform_identity_accounts pa ON pa\.github_id = a\.github_id WHERE (.+?) \), per_project AS/, 'leaderboard totals');
|
|
@@ -764,6 +796,19 @@ test('the cross-project leaderboard — only shown, public, active accounts, the
|
|
|
764
796
|
}
|
|
765
797
|
});
|
|
766
798
|
|
|
799
|
+
test('the builder of the week — only shown, public, active winners, on the crown and the past weeks, the same for every viewer', async () => {
|
|
800
|
+
const seen = [];
|
|
801
|
+
for (const who of [ANON, STRANGER, RECRUITER]) seen.push(await get(who, '/api/bongos/community/builder-of-the-week'));
|
|
802
|
+
for (const r of seen.slice(1)) assert.equal(r.text, seen[0].text, `${r.who} got a different builder of the week`);
|
|
803
|
+
const { winner, past } = seen[0].json;
|
|
804
|
+
assert.equal(winner && winner.handle, ACCT.PUB.handle, 'control: a shown winner is crowned, or absence below is vacuous');
|
|
805
|
+
const listed = new Set([winner, ...past].filter(Boolean).map((x) => x.handle));
|
|
806
|
+
for (const k of KEYS) {
|
|
807
|
+
assert.equal(listed.has(ACCT[k].handle), MATRIX[k].week === 'listed', `${k}: expected ${MATRIX[k].week}`);
|
|
808
|
+
}
|
|
809
|
+
assert.doesNotMatch(seen[0].text, /github_id/, 'github_id never crosses');
|
|
810
|
+
});
|
|
811
|
+
|
|
767
812
|
test('the recruiting directory — the recruiter sees the full and name-only tiers; Anon and Builder are refused', async () => {
|
|
768
813
|
const r = await get(RECRUITER, '/api/bongos/scouting');
|
|
769
814
|
assert.equal(r.status, 200);
|
|
@@ -806,7 +851,7 @@ function disclosed(r) {
|
|
|
806
851
|
}
|
|
807
852
|
|
|
808
853
|
test('the walk covered every surface for every row (so the claims below are about something)', () => {
|
|
809
|
-
for (const p of ['/profiles/', '/me/cross-project', '/community/search', '/community/leaderboard', '/scouting', '/guilds/mx-guild', '/guilds/mx-guild/members', '/projects/invite-guild']) {
|
|
854
|
+
for (const p of ['/profiles/', '/me/cross-project', '/community/search', '/community/leaderboard', '/community/builder-of-the-week', '/scouting', '/guilds/mx-guild', '/guilds/mx-guild/members', '/projects/invite-guild']) {
|
|
810
855
|
assert.ok(SEEN.some((r) => r.path.includes(p) && r.status === 200), `no 200 from ${p} — the sweep below would be vacuous for it`);
|
|
811
856
|
}
|
|
812
857
|
assert.ok(SEEN.length >= KEYS.length * 6, `only ${SEEN.length} responses recorded`);
|