@bongos/core 1.23.19 → 1.23.20
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 +62 -32
- 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 +107 -101
- package/docs/copy-registry.json +189 -125
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +3 -1
- package/docs/page-readings.json +73 -58
- package/modules/platform-identity/builder-of-the-week.js +18 -9
- package/modules/platform-identity/community-leaderboard.js +3 -0
- package/modules/platform-identity/community-movers.js +152 -0
- package/modules/platform-identity/routes/community-leaderboard.js +22 -2
- package/modules/public-landing/public/community-movers-none.states.json +15 -0
- package/modules/public-landing/public/community.html +62 -4
- package/modules/public-landing/public/community.states.json +1 -1
- package/modules/ui-design/kit/fixtures/community-movers-none.json +6 -0
- package/modules/ui-design/kit/fixtures/community-movers.json +12 -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/tests/community_movers.mjs +280 -0
- package/tests/community_movers_db.mjs +147 -0
- package/tests/hub_community_page.mjs +78 -9
- package/tests/privacy_matrix.mjs +48 -9
|
@@ -3253,5 +3253,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3253
3253
|
landed since 1.23.17 with no explicit bump. run 40611101597. (task 1002620)
|
|
3254
3254
|
1.23.19 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3255
3255
|
landed since 1.23.18 with no explicit bump. run 40613099968. (task 1002620)
|
|
3256
|
+
1.23.20 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3257
|
+
landed since 1.23.19 with no explicit bump. run 40709473534. (task 1002620)
|
|
3256
3258
|
---------------------------------------------------------------------------
|
|
3257
3259
|
```
|
package/docs/page-inventory.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"pages": 47,
|
|
14
14
|
"surfaces": 3,
|
|
15
15
|
"with_states": 40,
|
|
16
|
-
"states":
|
|
16
|
+
"states": 353
|
|
17
17
|
},
|
|
18
18
|
"surfaces": [
|
|
19
19
|
{
|
|
@@ -1456,6 +1456,7 @@
|
|
|
1456
1456
|
"modules/public-landing/public/community-crowns-new-year.states.json",
|
|
1457
1457
|
"modules/public-landing/public/community-crowns-none.states.json",
|
|
1458
1458
|
"modules/public-landing/public/community-empty.states.json",
|
|
1459
|
+
"modules/public-landing/public/community-movers-none.states.json",
|
|
1459
1460
|
"modules/public-landing/public/community-owner.states.json",
|
|
1460
1461
|
"modules/public-landing/public/community-owner-two.states.json",
|
|
1461
1462
|
"modules/public-landing/public/community-recruiter.states.json",
|
|
@@ -1482,6 +1483,7 @@
|
|
|
1482
1483
|
"out-home-new-year",
|
|
1483
1484
|
"out-home-none",
|
|
1484
1485
|
"out-empty",
|
|
1486
|
+
"out-home-movers-none",
|
|
1485
1487
|
"in-owner",
|
|
1486
1488
|
"in-owner-find",
|
|
1487
1489
|
"in-owner-two",
|
package/docs/page-readings.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"adr": "0341 D3",
|
|
6
6
|
"reread": "node scripts/bongos/page-reader.js --stale",
|
|
7
7
|
"how": ["Generated by node scripts/bongos/page-reader.js (BV2.TW04, task 1004315; ADR 0341 D3). Do not hand-edit.","One entry per page of docs/page-inventory.json. Lines are in page order: the default state first, and a line only another state shows sits where that state shows it, with its state named.","placement: placed = a docs/copy-registry.json row renders it (file, line, string_id); shared = shell text every page of the surface carries (top bar, sidebar, footer), with its row when the registry has one; unplaced = the registry cannot place it (a rewrite becomes an engineer task, ADR 0341 D4).","text keeps {…} where the registry row has a hole, or where a value the page read from its API was filled in. A string that is only such data is counted under omitted.data, not listed.","reading_hash is over the non-shared lines (key, section, text); files_hash is over the page's inventory files. --check compares files_hash only and never opens a browser.","Rendered through the hall-preview harness (hall) and the ui-design kit stub (landing, status), at 1280x800 with the clock pinned, so a reading is the fixture world's page."],
|
|
8
|
-
"counts": {"pages":47,"lines":
|
|
8
|
+
"counts": {"pages":47,"lines":5286,"placed":1607,"shared":419,"unplaced":3260,"omitted_data":3571},
|
|
9
9
|
"pages": [
|
|
10
10
|
{
|
|
11
11
|
"id": "builders:atlas",
|
|
@@ -4490,42 +4490,42 @@
|
|
|
4490
4490
|
"id": "landing:community",
|
|
4491
4491
|
"surface": "landing",
|
|
4492
4492
|
"title": "{{productName}} — Community",
|
|
4493
|
-
"states_read": ["out","in","out-board","out-30d","out-artists","out-community-band","in-community-band","in-projects-band","out-projects-band","out-week","in-week","out-find","out-find-search","out-recruiters","in-recruiters","in-board","out-home-first","out-home-new-year","out-home-none","out-empty","in-owner","in-owner-find","in-owner-two","in-owner-two-picked","in-recruiter","out-week-none","out-board-no-crown"],
|
|
4494
|
-
"files_hash": "
|
|
4495
|
-
"reading_hash": "
|
|
4496
|
-
"counts": {"lines":
|
|
4497
|
-
"omitted": {"data":
|
|
4493
|
+
"states_read": ["out","in","out-board","out-30d","out-artists","out-community-band","in-community-band","in-projects-band","out-projects-band","out-week","in-week","out-find","out-find-search","out-recruiters","in-recruiters","in-board","out-home-first","out-home-new-year","out-home-none","out-empty","out-home-movers-none","in-owner","in-owner-find","in-owner-two","in-owner-two-picked","in-recruiter","out-week-none","out-board-no-crown"],
|
|
4494
|
+
"files_hash": "9133cee62b885718",
|
|
4495
|
+
"reading_hash": "3cdb97deb6513c19",
|
|
4496
|
+
"counts": {"lines":132,"placed":52,"shared":12,"unplaced":68},
|
|
4497
|
+
"omitted": {"data":67},
|
|
4498
4498
|
"notes": [],
|
|
4499
4499
|
"lines": [
|
|
4500
4500
|
{"key":"L0001","section":"top bar","text":"Community","placement":"shared"},
|
|
4501
4501
|
{"key":"L0002","section":"top bar","text":"Projects","placement":"shared"},
|
|
4502
4502
|
{"key":"L0003","section":"top bar","text":"Review","placement":"shared","state":"in"},
|
|
4503
4503
|
{"key":"L0004","section":"page top","text":"Sky","placement":"unplaced","state":"in-projects-band"},
|
|
4504
|
-
{"key":"L0005","section":"page top","text":"the sky of every project","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4504
|
+
{"key":"L0005","section":"page top","text":"the sky of every project","placement":"placed","file":"modules/public-landing/public/community.html","line":200,"string_id":"8fa12f730019","state":"in-projects-band"},
|
|
4505
4505
|
{"key":"L0006","section":"page top","text":"Table","placement":"unplaced","state":"in-projects-band"},
|
|
4506
|
-
{"key":"L0007","section":"page top","text":"the same projects as rows","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4507
|
-
{"key":"L0008","section":"page top","text":"Your projects","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4508
|
-
{"key":"L0009","section":"page top","text":"the ones you own","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4509
|
-
{"key":"L0010","section":"page top","text":"New project","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4510
|
-
{"key":"L0011","section":"page top","text":"five questions and a home","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4511
|
-
{"key":"L0012","section":"top bar","text":"Sign in","placement":"shared","file":"modules/public-landing/public/community.html","line":
|
|
4512
|
-
{"key":"L0013","section":"Open to recruiters","text":"Open to recruiters","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4513
|
-
{"key":"L0014","section":"Open to recruiters","text":"Builders who keep their numbers hidden but are open to hearing from projects. Only project owners see who they are, and only by name.","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4506
|
+
{"key":"L0007","section":"page top","text":"the same projects as rows","placement":"placed","file":"modules/public-landing/public/community.html","line":201,"string_id":"21453b3ba5c3","state":"in-projects-band"},
|
|
4507
|
+
{"key":"L0008","section":"page top","text":"Your projects","placement":"placed","file":"modules/public-landing/public/community.html","line":202,"string_id":"4e25e6872321","state":"in-projects-band"},
|
|
4508
|
+
{"key":"L0009","section":"page top","text":"the ones you own","placement":"placed","file":"modules/public-landing/public/community.html","line":202,"string_id":"a0fa148c60a6","state":"in-projects-band"},
|
|
4509
|
+
{"key":"L0010","section":"page top","text":"New project","placement":"placed","file":"modules/public-landing/public/community.html","line":203,"string_id":"0a81a82d10c4","state":"in-projects-band"},
|
|
4510
|
+
{"key":"L0011","section":"page top","text":"five questions and a home","placement":"placed","file":"modules/public-landing/public/community.html","line":203,"string_id":"94a3462373d2","state":"in-projects-band"},
|
|
4511
|
+
{"key":"L0012","section":"top bar","text":"Sign in","placement":"shared","file":"modules/public-landing/public/community.html","line":181,"string_id":"ad80586f802a"},
|
|
4512
|
+
{"key":"L0013","section":"Open to recruiters","text":"Open to recruiters","placement":"placed","file":"modules/public-landing/public/community.html","line":192,"string_id":"1e116d8fe95a","state":"out-recruiters"},
|
|
4513
|
+
{"key":"L0014","section":"Open to recruiters","text":"Builders who keep their numbers hidden but are open to hearing from projects. Only project owners see who they are, and only by name.","placement":"placed","file":"modules/public-landing/public/community.html","line":341,"string_id":"932bb6ad495b","state":"out-recruiters"},
|
|
4514
4514
|
{"key":"L0015","section":"Open to recruiters","text":"GitHub: {…}","placement":"unplaced","state":"in-recruiter","occurrences":3},
|
|
4515
|
-
{"key":"L0016","section":"Open to recruiters","text":"Only project owners can see this list.","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4516
|
-
{"key":"L0017","section":"Open to recruiters","text":"Sign in to see whether this list is open to you.","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4517
|
-
{"key":"L0018","section":"Open to recruiters","text":"Sign in with GitHub","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4518
|
-
{"key":"L0019","section":"footer","text":"A non-profit, capture-resistant commons.","placement":"shared","file":"modules/public-landing/public/community.html","line":
|
|
4519
|
-
{"key":"L0020","section":"footer","text":"Built by agents, on Cloud Bongos.","placement":"shared","file":"modules/public-landing/public/community.html","line":
|
|
4520
|
-
{"key":"L0021","section":"footer","text":"Open source","placement":"shared","file":"modules/public-landing/public/community.html","line":
|
|
4515
|
+
{"key":"L0016","section":"Open to recruiters","text":"Only project owners can see this list.","placement":"placed","file":"modules/public-landing/public/community.html","line":350,"string_id":"6bcd385b13d5","state":"in-recruiters"},
|
|
4516
|
+
{"key":"L0017","section":"Open to recruiters","text":"Sign in to see whether this list is open to you.","placement":"placed","file":"modules/public-landing/public/community.html","line":344,"string_id":"f59de6034af4","state":"out-recruiters"},
|
|
4517
|
+
{"key":"L0018","section":"Open to recruiters","text":"Sign in with GitHub","placement":"placed","file":"modules/public-landing/public/community.html","line":346,"string_id":"df38941b25a6","state":"out-recruiters"},
|
|
4518
|
+
{"key":"L0019","section":"footer","text":"A non-profit, capture-resistant commons.","placement":"shared","file":"modules/public-landing/public/community.html","line":358,"string_id":"afe83bbbd0d4","state":"out-recruiters","occurrences":6},
|
|
4519
|
+
{"key":"L0020","section":"footer","text":"Built by agents, on Cloud Bongos.","placement":"shared","file":"modules/public-landing/public/community.html","line":358,"string_id":"bd315dd71ea7","state":"out-recruiters","occurrences":6},
|
|
4520
|
+
{"key":"L0021","section":"footer","text":"Open source","placement":"shared","file":"modules/public-landing/public/community.html","line":358,"string_id":"d6314ddcc1d0","state":"out-recruiters","occurrences":6},
|
|
4521
4521
|
{"key":"L0022","section":"footer","text":"AGPL-3.0","placement":"shared","state":"out-recruiters","occurrences":6},
|
|
4522
4522
|
{"key":"L0023","section":"navigation","text":"Privacy","placement":"shared","state":"out-recruiters","occurrences":6},
|
|
4523
4523
|
{"key":"L0024","section":"navigation","text":"Terms","placement":"shared","state":"out-recruiters","occurrences":6},
|
|
4524
4524
|
{"key":"L0025","section":"navigation","text":"Contact","placement":"shared","state":"out-recruiters","occurrences":6},
|
|
4525
|
-
{"key":"L0026","section":"navigation","text":"Builders' hall","placement":"shared","file":"modules/public-landing/public/community.html","line":
|
|
4526
|
-
{"key":"L0027","section":"Find a builder","text":"Find a builder","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4527
|
-
{"key":"L0028","section":"Find a builder","text":"Their name or handle","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4528
|
-
{"key":"L0029","section":"Find a builder","text":"Any role","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4525
|
+
{"key":"L0026","section":"navigation","text":"Builders' hall","placement":"shared","file":"modules/public-landing/public/community.html","line":358,"string_id":"90a128c97eae","state":"out-recruiters","occurrences":6},
|
|
4526
|
+
{"key":"L0027","section":"Find a builder","text":"Find a builder","placement":"placed","file":"modules/public-landing/public/community.html","line":191,"string_id":"f1e93ba98ceb","state":"out-find"},
|
|
4527
|
+
{"key":"L0028","section":"Find a builder","text":"Their name or handle","placement":"placed","file":"modules/public-landing/public/community.html","line":315,"string_id":"541d5a7612a9","attr":"placeholder","state":"out-find"},
|
|
4528
|
+
{"key":"L0029","section":"Find a builder","text":"Any role","placement":"placed","file":"modules/public-landing/public/community.html","line":319,"string_id":"648862c67a23","state":"out-find"},
|
|
4529
4529
|
{"key":"L0030","section":"Find a builder","text":"Engineers","placement":"unplaced","state":"out-find"},
|
|
4530
4530
|
{"key":"L0031","section":"Find a builder","text":"Artists","placement":"unplaced","state":"out-find"},
|
|
4531
4531
|
{"key":"L0032","section":"Find a builder","text":"Ideators","placement":"unplaced","state":"out-find"},
|
|
@@ -4535,32 +4535,32 @@
|
|
|
4535
4535
|
{"key":"L0036","section":"Find a builder","text":"2 builders found.","placement":"unplaced","state":"out-find-search"},
|
|
4536
4536
|
{"key":"L0037","section":"Find a builder","text":"{…} · designer","placement":"unplaced","state":"out-find-search"},
|
|
4537
4537
|
{"key":"L0038","section":"Find a builder","text":"Recruit","placement":"unplaced","state":"in-owner-find","occurrences":2},
|
|
4538
|
-
{"key":"L0039","section":"page top","text":"Community home","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4539
|
-
{"key":"L0040","section":"page top","text":"the builders of the week, month and year","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4538
|
+
{"key":"L0039","section":"page top","text":"Community home","placement":"placed","file":"modules/public-landing/public/community.html","line":188,"string_id":"d380e1ef9508","state":"out-community-band"},
|
|
4539
|
+
{"key":"L0040","section":"page top","text":"the builders of the week, month and year","placement":"placed","file":"modules/public-landing/public/community.html","line":188,"string_id":"10dac85c9e17","state":"out-community-band"},
|
|
4540
4540
|
{"key":"L0041","section":"page top","text":"Leaderboard","placement":"unplaced","state":"out-community-band"},
|
|
4541
|
-
{"key":"L0042","section":"page top","text":"who earned the most, across every project","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4542
|
-
{"key":"L0043","section":"page top","text":"Builder of the week","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4543
|
-
{"key":"L0044","section":"page top","text":"the top earner of each week, and how it is chosen","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4544
|
-
{"key":"L0045","section":"page top","text":"Find a builder","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4545
|
-
{"key":"L0046","section":"page top","text":"anyone building here, by name or role","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4546
|
-
{"key":"L0047","section":"page top","text":"Open to recruiters","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4547
|
-
{"key":"L0048","section":"page top","text":"who is open to recruiting, for project owners","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4548
|
-
{"key":"L0049","section":"page top","text":"Builders you may know","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4549
|
-
{"key":"L0050","section":"page top","text":"connections or projects in common","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4541
|
+
{"key":"L0042","section":"page top","text":"who earned the most, across every project","placement":"placed","file":"modules/public-landing/public/community.html","line":189,"string_id":"776e80cf232d","state":"out-community-band"},
|
|
4542
|
+
{"key":"L0043","section":"page top","text":"Builder of the week","placement":"placed","file":"modules/public-landing/public/community.html","line":190,"string_id":"39b61cc5172e","state":"out-community-band"},
|
|
4543
|
+
{"key":"L0044","section":"page top","text":"the top earner of each week, and how it is chosen","placement":"placed","file":"modules/public-landing/public/community.html","line":190,"string_id":"15237c234a72","state":"out-community-band"},
|
|
4544
|
+
{"key":"L0045","section":"page top","text":"Find a builder","placement":"placed","file":"modules/public-landing/public/community.html","line":191,"string_id":"f1e93ba98ceb","state":"out-community-band"},
|
|
4545
|
+
{"key":"L0046","section":"page top","text":"anyone building here, by name or role","placement":"placed","file":"modules/public-landing/public/community.html","line":191,"string_id":"a4e84a1501c5","state":"out-community-band"},
|
|
4546
|
+
{"key":"L0047","section":"page top","text":"Open to recruiters","placement":"placed","file":"modules/public-landing/public/community.html","line":192,"string_id":"1e116d8fe95a","state":"out-community-band"},
|
|
4547
|
+
{"key":"L0048","section":"page top","text":"who is open to recruiting, for project owners","placement":"placed","file":"modules/public-landing/public/community.html","line":192,"string_id":"15ac45627fab","state":"out-community-band"},
|
|
4548
|
+
{"key":"L0049","section":"page top","text":"Builders you may know","placement":"placed","file":"modules/public-landing/public/community.html","line":193,"string_id":"724de83c77c6","state":"in-community-band"},
|
|
4549
|
+
{"key":"L0050","section":"page top","text":"connections or projects in common","placement":"placed","file":"modules/public-landing/public/community.html","line":193,"string_id":"171cee573b19","state":"in-community-band"},
|
|
4550
4550
|
{"key":"L0051","section":"Leaderboard","text":"Leaderboard","placement":"unplaced","state":"out-board"},
|
|
4551
|
-
{"key":"L0052","section":"Leaderboard","text":"Builder of the week","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4551
|
+
{"key":"L0052","section":"Leaderboard","text":"Builder of the week","placement":"placed","file":"modules/public-landing/public/community.html","line":190,"string_id":"39b61cc5172e","state":"out-board"},
|
|
4552
4552
|
{"key":"L0053","section":"Leaderboard","text":", 1,240 {…}, 19–25 Oct","placement":"unplaced","state":"out-board"},
|
|
4553
4553
|
{"key":"L0054","section":"Leaderboard","text":"Everyone","placement":"unplaced","state":"out-board"},
|
|
4554
4554
|
{"key":"L0055","section":"Leaderboard","text":"Engineers","placement":"unplaced","state":"out-board"},
|
|
4555
4555
|
{"key":"L0056","section":"Leaderboard","text":"Artists","placement":"unplaced","state":"out-board"},
|
|
4556
4556
|
{"key":"L0057","section":"Leaderboard","text":"Ideators","placement":"unplaced","state":"out-board"},
|
|
4557
4557
|
{"key":"L0058","section":"Leaderboard","text":"Designers","placement":"unplaced","state":"out-board"},
|
|
4558
|
-
{"key":"L0059","section":"Leaderboard","text":"Last 7 days","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4559
|
-
{"key":"L0060","section":"Leaderboard","text":"Last 30 days","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4560
|
-
{"key":"L0061","section":"Leaderboard","text":"Past year","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4561
|
-
{"key":"L0062","section":"Leaderboard","text":"All time","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4558
|
+
{"key":"L0059","section":"Leaderboard","text":"Last 7 days","placement":"placed","file":"modules/public-landing/public/community.html","line":276,"string_id":"fc3582ac098d","state":"out-board"},
|
|
4559
|
+
{"key":"L0060","section":"Leaderboard","text":"Last 30 days","placement":"placed","file":"modules/public-landing/public/community.html","line":277,"string_id":"52f6b5974994","state":"out-board"},
|
|
4560
|
+
{"key":"L0061","section":"Leaderboard","text":"Past year","placement":"placed","file":"modules/public-landing/public/community.html","line":278,"string_id":"42a9479b375c","state":"out-board"},
|
|
4561
|
+
{"key":"L0062","section":"Leaderboard","text":"All time","placement":"placed","file":"modules/public-landing/public/community.html","line":279,"string_id":"23c761dcff62","state":"out-board"},
|
|
4562
4562
|
{"key":"L0063","section":"Leaderboard","text":"Credits earned in the last 30 days, across every project.","placement":"unplaced","state":"out-30d"},
|
|
4563
|
-
{"key":"L0064","section":"Leaderboard","text":"{{Currency}} earned across every project, since each builder started.","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4563
|
+
{"key":"L0064","section":"Leaderboard","text":"{{Currency}} earned across every project, since each builder started.","placement":"placed","file":"modules/public-landing/public/community.html","line":283,"string_id":"f81129627f7b","state":"out-board"},
|
|
4564
4564
|
{"key":"L0065","section":"Leaderboard","text":"Recruit to","placement":"unplaced","state":"in-owner-two"},
|
|
4565
4565
|
{"key":"L0066","section":"Leaderboard","text":"Recruit opens its hall with their name filled in, and you send the invite there.","placement":"unplaced","state":"in-owner-two"},
|
|
4566
4566
|
{"key":"L0067","section":"Leaderboard","text":"Recruit opens {…}’s hall with their name filled in, and you send the invite there.","placement":"unplaced","state":"in-owner"},
|
|
@@ -4574,19 +4574,19 @@
|
|
|
4574
4574
|
{"key":"L0075","section":"Leaderboard","text":"31 works shipped · 18 karma","placement":"unplaced","state":"out-board"},
|
|
4575
4575
|
{"key":"L0076","section":"Leaderboard","text":"12 works shipped · 7 karma","placement":"unplaced","state":"out-board"},
|
|
4576
4576
|
{"key":"L0077","section":"Leaderboard","text":"1 work shipped · 0 karma","placement":"unplaced","state":"out-board"},
|
|
4577
|
-
{"key":"L0078","section":"Leaderboard","text":"A project reports a builder’s numbers when they sign in or ship there, so a window counts work when it was reported, not when it was done.","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4578
|
-
{"key":"L0079","section":"Top builders","text":"Top builders","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4579
|
-
{"key":"L0080","section":"Builder of the week","text":"Builder of the week","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4577
|
+
{"key":"L0078","section":"Leaderboard","text":"A project reports a builder’s numbers when they sign in or ship there, so a window counts work when it was reported, not when it was done.","placement":"placed","file":"modules/public-landing/public/community.html","line":289,"string_id":"8d79db29b7d6","state":"out-board"},
|
|
4578
|
+
{"key":"L0079","section":"Top builders","text":"Top builders","placement":"placed","file":"modules/public-landing/public/community.html","line":232,"string_id":"bb89654b6d4f"},
|
|
4579
|
+
{"key":"L0080","section":"Builder of the week","text":"Builder of the week","placement":"placed","file":"modules/public-landing/public/community.html","line":190,"string_id":"39b61cc5172e"},
|
|
4580
4580
|
{"key":"L0081","section":"Builder of the week","text":"No {…} of the week for 19–25 Oct 2026.","placement":"unplaced","state":"out-week-none"},
|
|
4581
4581
|
{"key":"L0082","section":"Builder of the week","text":"4–10 Jan 2027","placement":"unplaced","state":"out-home-new-year"},
|
|
4582
4582
|
{"key":"L0083","section":"Builder of the week","text":"19–25 Oct 2026","placement":"unplaced","state":"out-week"},
|
|
4583
4583
|
{"key":"L0084","section":"Builder of the week","text":"26 Oct – 1 Nov 2026","placement":"unplaced"},
|
|
4584
4584
|
{"key":"L0085","section":"Builder of the week","text":"No {…} of the week for 26 Oct – 1 Nov 2026.","placement":"unplaced","state":"out-home-none"},
|
|
4585
4585
|
{"key":"L0086","section":"Builder of the week","text":"works shipped","placement":"unplaced"},
|
|
4586
|
-
{"key":"L0087","section":"How it is chosen","text":"How it is chosen","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4587
|
-
{"key":"L0088","section":"How it is chosen","text":"The builder who earned the most {{currency}} across every project in one week, Monday to Sunday (UTC), among the builders who shipped at least one work that week. A tie goes to whoever shipped more works, then to whoever earned more karma. Builders who hide their numbers or keep their account private are not counted.","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4588
|
-
{"key":"L0089","section":"How it is chosen","text":"Each week is decided once, the first time a community page asks for it after it ends, and it does not change after that. A project reports a builder’s numbers when they sign in or ship there, so a week counts work when it was reported, not when it was done.","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4589
|
-
{"key":"L0090","section":"Past weeks","text":"Past weeks","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4586
|
+
{"key":"L0087","section":"How it is chosen","text":"How it is chosen","placement":"placed","file":"modules/public-landing/public/community.html","line":302,"string_id":"104fbd1c98fd","state":"out-week"},
|
|
4587
|
+
{"key":"L0088","section":"How it is chosen","text":"The builder who earned the most {{currency}} across every project in one week, Monday to Sunday (UTC), among the builders who shipped at least one work that week. A tie goes to whoever shipped more works, then to whoever earned more karma. Builders who hide their numbers or keep their account private are not counted.","placement":"placed","file":"modules/public-landing/public/community.html","line":303,"string_id":"5fe47a649523","state":"out-week"},
|
|
4588
|
+
{"key":"L0089","section":"How it is chosen","text":"Each week is decided once, the first time a community page asks for it after it ends, and it does not change after that. A project reports a builder’s numbers when they sign in or ship there, so a week counts work when it was reported, not when it was done.","placement":"placed","file":"modules/public-landing/public/community.html","line":304,"string_id":"b451221d44da","state":"out-week"},
|
|
4589
|
+
{"key":"L0090","section":"Past weeks","text":"Past weeks","placement":"placed","file":"modules/public-landing/public/community.html","line":305,"string_id":"5825a37975e0","state":"out-week"},
|
|
4590
4590
|
{"key":"L0091","section":"Past weeks","text":"12–18 Oct","placement":"unplaced","state":"out-week"},
|
|
4591
4591
|
{"key":"L0092","section":"Past weeks","text":"9 works shipped","placement":"unplaced","state":"out-week"},
|
|
4592
4592
|
{"key":"L0093","section":"Past weeks","text":"5–11 Oct","placement":"unplaced","state":"out-week"},
|
|
@@ -4595,25 +4595,40 @@
|
|
|
4595
4595
|
{"key":"L0096","section":"Past weeks","text":"6 works shipped","placement":"unplaced","state":"out-week"},
|
|
4596
4596
|
{"key":"L0097","section":"Past weeks","text":"14–20 Sep","placement":"unplaced","state":"out-week"},
|
|
4597
4597
|
{"key":"L0098","section":"Past weeks","text":"2 works shipped","placement":"unplaced","state":"out-week"},
|
|
4598
|
-
{"key":"L0099","section":"Builder of the month","text":"Builder of the month","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4598
|
+
{"key":"L0099","section":"Builder of the month","text":"Builder of the month","placement":"placed","file":"modules/public-landing/public/community.html","line":237,"string_id":"9e64afc8d912"},
|
|
4599
4599
|
{"key":"L0100","section":"Builder of the month","text":"December 2026","placement":"unplaced","state":"out-home-new-year"},
|
|
4600
4600
|
{"key":"L0101","section":"Builder of the month","text":"September 2026","placement":"unplaced","state":"out-home-first"},
|
|
4601
4601
|
{"key":"L0102","section":"Builder of the month","text":"The first {…} of the month is named on 1 Nov 2026.","placement":"unplaced","state":"out-home-first"},
|
|
4602
4602
|
{"key":"L0103","section":"Builder of the month","text":"October 2026","placement":"unplaced"},
|
|
4603
4603
|
{"key":"L0104","section":"Builder of the month","text":"No {…} of the month for October 2026.","placement":"unplaced","state":"out-home-none"},
|
|
4604
4604
|
{"key":"L0105","section":"Builder of the month","text":"works shipped","placement":"unplaced"},
|
|
4605
|
-
{"key":"L0106","section":"Builder of the year","text":"Builder of the year","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4605
|
+
{"key":"L0106","section":"Builder of the year","text":"Builder of the year","placement":"placed","file":"modules/public-landing/public/community.html","line":238,"string_id":"bb31301c010e"},
|
|
4606
4606
|
{"key":"L0107","section":"Builder of the year","text":"2027 so far","placement":"unplaced","state":"out-home-new-year"},
|
|
4607
4607
|
{"key":"L0108","section":"Builder of the year","text":"2026 so far, since 27 Sep 2026","placement":"unplaced"},
|
|
4608
4608
|
{"key":"L0109","section":"Builder of the year","text":"No {…} of 2026 yet.","placement":"unplaced","state":"out-home-none"},
|
|
4609
4609
|
{"key":"L0110","section":"Builder of the year","text":"works shipped","placement":"unplaced"},
|
|
4610
|
-
{"key":"L0111","section":"Builder of the year","text":"Each is the builder who earned the most {{currency}} across every project in that time, among the builders who shipped at least one work in it. A tie goes to whoever shipped more works, then to whoever earned more karma. Builders who hide their numbers or keep their account private are not counted. The week and the month are decided once, when they end; the year shows the leader so far, and is decided on 1 January.","placement":"placed","file":"modules/public-landing/public/community.html","line":
|
|
4611
|
-
{"key":"L0112","section":"
|
|
4612
|
-
{"key":"L0113","section":"
|
|
4613
|
-
{"key":"L0114","section":"
|
|
4614
|
-
{"key":"L0115","section":"
|
|
4615
|
-
{"key":"L0116","section":"
|
|
4616
|
-
{"key":"L0117","section":"
|
|
4610
|
+
{"key":"L0111","section":"Builder of the year","text":"Each is the builder who earned the most {{currency}} across every project in that time, among the builders who shipped at least one work in it. A tie goes to whoever shipped more works, then to whoever earned more karma. Builders who hide their numbers or keep their account private are not counted. The week and the month are decided once, when they end; the year shows the leader so far, and is decided on 1 January.","placement":"placed","file":"modules/public-landing/public/community.html","line":240,"string_id":"544a3fb1602a"},
|
|
4611
|
+
{"key":"L0112","section":"Rising builder","text":"Rising builder","placement":"placed","file":"modules/public-landing/public/community.html","line":245,"string_id":"a09a9f7fe86d"},
|
|
4612
|
+
{"key":"L0113","section":"Rising builder","text":"The last 30 days","placement":"placed","file":"modules/public-landing/public/community.html","line":245,"string_id":"e6ee07ea9204"},
|
|
4613
|
+
{"key":"L0114","section":"Rising builder","text":"No rising {…} in the last 30 days.","placement":"unplaced","state":"out-home-movers-none"},
|
|
4614
|
+
{"key":"L0115","section":"Rising builder","text":"works shipped","placement":"unplaced"},
|
|
4615
|
+
{"key":"L0116","section":"Projects on the move","text":"Projects on the move","placement":"placed","file":"modules/public-landing/public/community.html","line":246,"string_id":"c99aad168284"},
|
|
4616
|
+
{"key":"L0117","section":"Projects on the move","text":"The last 30 days","placement":"placed","file":"modules/public-landing/public/community.html","line":245,"string_id":"e6ee07ea9204"},
|
|
4617
|
+
{"key":"L0118","section":"Projects on the move","text":"No public project shipped a work in the last 30 days.","placement":"placed","file":"modules/public-landing/public/community.html","line":246,"string_id":"cf4c05abde4f","state":"out-home-movers-none"},
|
|
4618
|
+
{"key":"L0119","section":"Projects on the move","text":"1,840 {…} earned","placement":"unplaced"},
|
|
4619
|
+
{"key":"L0120","section":"Projects on the move","text":"works shipped","placement":"unplaced","occurrences":4},
|
|
4620
|
+
{"key":"L0121","section":"Projects on the move","text":"2,210 {…} earned","placement":"unplaced"},
|
|
4621
|
+
{"key":"L0122","section":"Projects on the move","text":"610 {…} earned","placement":"unplaced"},
|
|
4622
|
+
{"key":"L0123","section":"Projects on the move","text":"380 {…} earned","placement":"unplaced"},
|
|
4623
|
+
{"key":"L0124","section":"Projects on the move","text":"45 {…} earned","placement":"unplaced"},
|
|
4624
|
+
{"key":"L0125","section":"Projects on the move","text":"work shipped","placement":"unplaced"},
|
|
4625
|
+
{"key":"L0126","section":"Projects on the move","text":"The rising builder is the one who earned the most {{currency}} in the last 30 days, among the builders who had earned under 100 {{currency}} before them and shipped at least one work in them. Projects on the move are the public projects that shipped the most works in the last 30 days, then earned the most {{currency}}. Builders who hide their numbers or keep their account private are not counted in either.","placement":"placed","file":"modules/public-landing/public/community.html","line":248,"string_id":"3ff62ec88969"},
|
|
4626
|
+
{"key":"L0127","section":"Community pages","text":"Community pages","placement":"placed","file":"modules/public-landing/public/community.html","line":249,"string_id":"f195f790ab49"},
|
|
4627
|
+
{"key":"L0128","section":"Community pages","text":"Leaderboard","placement":"unplaced"},
|
|
4628
|
+
{"key":"L0129","section":"Community pages","text":"Builder of the week","placement":"placed","file":"modules/public-landing/public/community.html","line":190,"string_id":"39b61cc5172e"},
|
|
4629
|
+
{"key":"L0130","section":"Community pages","text":"Find a builder","placement":"placed","file":"modules/public-landing/public/community.html","line":191,"string_id":"f1e93ba98ceb"},
|
|
4630
|
+
{"key":"L0131","section":"Community pages","text":"Open to recruiters","placement":"placed","file":"modules/public-landing/public/community.html","line":192,"string_id":"1e116d8fe95a"},
|
|
4631
|
+
{"key":"L0132","section":"Community pages","text":"Builders you may know","placement":"placed","file":"modules/public-landing/public/community.html","line":193,"string_id":"724de83c77c6","state":"in"}
|
|
4617
4632
|
]
|
|
4618
4633
|
},
|
|
4619
4634
|
{
|
|
@@ -53,13 +53,15 @@ function crownedWeek(now = new Date()) {
|
|
|
53
53
|
|
|
54
54
|
const isoDate = (d) => d.toISOString().slice(0, 10);
|
|
55
55
|
|
|
56
|
-
// The
|
|
57
|
-
// and year's (
|
|
56
|
+
// The per-(project, builder) deltas over [$1, $2] among eligible builders, as the CTEs
|
|
57
|
+
// `act` and `per_project`: the week's, month's and year's (computeWinner below) and the 30
|
|
58
|
+
// days' (community-movers.js). Arithmetic is bigint for the leaderboard's reason: the
|
|
58
59
|
// columns are instance-reported int4. `fromFirstSnapshot` is the YEAR's rule (owner,
|
|
59
60
|
// 2026-10-27): a row with no snapshot before the start, and not first seen inside, counts
|
|
60
61
|
// from its first snapshot inside instead of not at all, since history began in late
|
|
61
62
|
// September 2026 and the rule above would leave that year to new builders alone.
|
|
62
|
-
|
|
63
|
+
// `base_credits` is what a row had at the start by the same rule.
|
|
64
|
+
function periodRowsSql({ fromFirstSnapshot = false } = {}) {
|
|
63
65
|
const base = (col) => (fromFirstSnapshot
|
|
64
66
|
? `COALESCE(b.${col}, CASE WHEN act.first_seen_at >= $1::timestamptz THEN 0 ELSE f.${col} END, 0)`
|
|
65
67
|
: `COALESCE(b.${col}, 0)`);
|
|
@@ -74,17 +76,17 @@ async function computeWinner(start, end, pool, { fromFirstSnapshot = false } = {
|
|
|
74
76
|
LIMIT 1
|
|
75
77
|
) f ON true`
|
|
76
78
|
: '';
|
|
77
|
-
|
|
78
|
-
`WITH act AS (
|
|
79
|
+
return `WITH act AS (
|
|
79
80
|
SELECT a.client_id, a.github_id, a.first_seen_at, pa.handle, pa.github_login
|
|
80
81
|
FROM platform_identity_builder_project_activity a
|
|
81
82
|
JOIN platform_identity_accounts pa ON pa.github_id = a.github_id
|
|
82
83
|
WHERE ${rollupVisibleSql('pa')}
|
|
83
84
|
), per_project AS (
|
|
84
|
-
SELECT act.github_id, act.handle, act.github_login,
|
|
85
|
+
SELECT act.client_id, act.github_id, act.handle, act.github_login,
|
|
85
86
|
COALESCE(e.credits, 0)::bigint - ${base('credits')} AS credits,
|
|
86
87
|
COALESCE(e.tasks_shipped, 0)::bigint - ${base('tasks_shipped')} AS tasks_shipped,
|
|
87
|
-
COALESCE(e.karma, 0)::bigint - ${base('karma')} AS karma
|
|
88
|
+
COALESCE(e.karma, 0)::bigint - ${base('karma')} AS karma,
|
|
89
|
+
${base('credits')}::bigint AS base_credits
|
|
88
90
|
FROM act
|
|
89
91
|
LEFT JOIN LATERAL (
|
|
90
92
|
SELECT s.credits, s.tasks_shipped, s.karma
|
|
@@ -103,7 +105,13 @@ async function computeWinner(start, end, pool, { fromFirstSnapshot = false } = {
|
|
|
103
105
|
LIMIT 1
|
|
104
106
|
) b ON true${firstInside}
|
|
105
107
|
WHERE b.credits IS NOT NULL OR act.first_seen_at >= $1::timestamptz${fromFirstSnapshot ? ' OR f.credits IS NOT NULL' : ''}
|
|
106
|
-
)
|
|
108
|
+
)`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// The winner of [start, end) among eligible builders, or null.
|
|
112
|
+
async function computeWinner(start, end, pool, { fromFirstSnapshot = false } = {}) {
|
|
113
|
+
const { rows } = await pool.query(
|
|
114
|
+
`${periodRowsSql({ fromFirstSnapshot })}
|
|
107
115
|
SELECT github_id, sum(credits)::bigint AS credits, sum(tasks_shipped)::bigint AS tasks_shipped
|
|
108
116
|
FROM per_project
|
|
109
117
|
GROUP BY github_id, handle, github_login
|
|
@@ -201,8 +209,9 @@ module.exports = {
|
|
|
201
209
|
crownedWeek,
|
|
202
210
|
clearCrownMemo,
|
|
203
211
|
ARCHIVE_WEEKS,
|
|
204
|
-
// for community-crowns.js (the month and the year)
|
|
212
|
+
// for community-crowns.js (the month and the year) and community-movers.js (the 30 days)
|
|
205
213
|
computeWinner,
|
|
214
|
+
periodRowsSql,
|
|
206
215
|
crownOnce,
|
|
207
216
|
isoDate,
|
|
208
217
|
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// modules/platform-identity/community-movers.js — the rising builder and the projects on the
|
|
2
|
+
// move, the read behind GET /community/movers for the row under the crowns on the hub's
|
|
3
|
+
// /community home (community spec D7 items 1–2; task 1002983, goal 1000110 CT7).
|
|
4
|
+
//
|
|
5
|
+
// THE 30 DAYS are rolling, up to now, and read from the snapshot history by the year's
|
|
6
|
+
// baseline rule (builder-of-the-week.js periodRowsSql, fromFirstSnapshot): a row first seen
|
|
7
|
+
// inside counts from zero, a row with no snapshot before them from its first one inside (an
|
|
8
|
+
// undercount, never a lifetime).
|
|
9
|
+
//
|
|
10
|
+
// THE RISING BUILDER (owner, 2026-10-28): the most credits earned in the 30 days among the
|
|
11
|
+
// builders who had under RISING_BASE_MAX before them, with at least one work shipped in
|
|
12
|
+
// them; ties as the crowns. "Before" is each row's baseline, and a row with no history in
|
|
13
|
+
// the 30 days at all counts its live total, so a veteran's quiet projects still count.
|
|
14
|
+
//
|
|
15
|
+
// THE PROJECTS ON THE MOVE (owner, 2026-10-28): the most works shipped in the 30 days, then
|
|
16
|
+
// credits, the top PROJECTS_MAX. Only the builders the leaderboard may show are counted
|
|
17
|
+
// (spec D8), and a project is named only where a profile may name it: on the map with the
|
|
18
|
+
// Public door (task 1003558); private and stealth projects are left off, not anonymised.
|
|
19
|
+
//
|
|
20
|
+
// TWO HALVES, as the leaderboard: the 30 days' rows (one snapshot probe per activity row)
|
|
21
|
+
// are memoised per pool for MOVERS_TTL_MS; who and which projects may be shown is asked on
|
|
22
|
+
// every read, so a hide, a private account or a closed door beats the memo. Viewer-
|
|
23
|
+
// independent; github_id and client_id never cross the projection.
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
const { pool: defaultPool } = require('../../src/module-api');
|
|
27
|
+
const { periodRowsSql } = require('./builder-of-the-week');
|
|
28
|
+
const { eligibleIdentities, compareRows, windowStart } = require('./community-leaderboard');
|
|
29
|
+
const { onMapSql } = require('./projects-feed');
|
|
30
|
+
|
|
31
|
+
const WINDOW = '30d';
|
|
32
|
+
const RISING_BASE_MAX = 100;
|
|
33
|
+
const PROJECTS_MAX = 5;
|
|
34
|
+
const MOVERS_TTL_MS = 30 * 1000;
|
|
35
|
+
|
|
36
|
+
// Every eligible (project, builder) row's 30 days: [{ clientId, githubId, credits,
|
|
37
|
+
// tasks_shipped, karma, base }]. A row the period rule leaves out moved nowhere in them, so
|
|
38
|
+
// its deltas are zero and its baseline is its live total.
|
|
39
|
+
async function computeRows(start, end, pool) {
|
|
40
|
+
const { rows } = await pool.query(
|
|
41
|
+
`${periodRowsSql({ fromFirstSnapshot: true })}
|
|
42
|
+
SELECT a.client_id, a.github_id, pp.credits, pp.tasks_shipped, pp.karma,
|
|
43
|
+
COALESCE(pp.base_credits, a.credits::bigint) AS base_credits
|
|
44
|
+
FROM act
|
|
45
|
+
JOIN platform_identity_builder_project_activity a
|
|
46
|
+
ON a.client_id = act.client_id AND a.github_id = act.github_id
|
|
47
|
+
LEFT JOIN per_project pp
|
|
48
|
+
ON pp.client_id = act.client_id AND pp.github_id = act.github_id`,
|
|
49
|
+
[start, end],
|
|
50
|
+
);
|
|
51
|
+
return rows.map((r) => ({
|
|
52
|
+
clientId: String(r.client_id),
|
|
53
|
+
githubId: String(r.github_id),
|
|
54
|
+
credits: Number(r.credits || 0),
|
|
55
|
+
tasks_shipped: Number(r.tasks_shipped || 0),
|
|
56
|
+
karma: Number(r.karma || 0),
|
|
57
|
+
base: Number(r.base_credits || 0),
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// pool → { at, start, end, promise }. Concurrent reads share one in-flight computation; a
|
|
62
|
+
// failed one is dropped so the next read retries.
|
|
63
|
+
const rowsMemo = new Map();
|
|
64
|
+
function windowRows(now, pool) {
|
|
65
|
+
const m = rowsMemo.get(pool);
|
|
66
|
+
const at = now.getTime();
|
|
67
|
+
if (m && at >= m.at && at - m.at < MOVERS_TTL_MS) return m;
|
|
68
|
+
const start = windowStart(WINDOW, now);
|
|
69
|
+
const entry = { at, start, end: now, promise: computeRows(start, now, pool) };
|
|
70
|
+
entry.promise.catch(() => { if (rowsMemo.get(pool) === entry) rowsMemo.delete(pool); });
|
|
71
|
+
rowsMemo.set(pool, entry);
|
|
72
|
+
return entry;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The projects among `clientIds` a profile may name, now: Map clientId → { name, origin }.
|
|
76
|
+
async function namedProjects(clientIds, pool) {
|
|
77
|
+
if (clientIds.length === 0) return new Map();
|
|
78
|
+
const { rows } = await pool.query(
|
|
79
|
+
`SELECT c.client_id, p.name, p.origin
|
|
80
|
+
FROM platform_identity_sso_clients c
|
|
81
|
+
JOIN platform_identity_projects p ON p.origin = c.origin
|
|
82
|
+
WHERE c.client_id = ANY($1::text[])
|
|
83
|
+
AND ${onMapSql('p')} AND p.visibility = 'public'`,
|
|
84
|
+
[clientIds],
|
|
85
|
+
);
|
|
86
|
+
return new Map(rows.map((r) => [String(r.client_id), { name: r.name, origin: r.origin }]));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Sums `rows` per `key`, from zero.
|
|
90
|
+
function sumBy(rows, key) {
|
|
91
|
+
const out = new Map();
|
|
92
|
+
for (const r of rows) {
|
|
93
|
+
const k = r[key];
|
|
94
|
+
const s = out.get(k) || { credits: 0, tasks_shipped: 0, karma: 0, base: 0 };
|
|
95
|
+
s.credits += r.credits;
|
|
96
|
+
s.tasks_shipped += r.tasks_shipped;
|
|
97
|
+
s.karma += r.karma;
|
|
98
|
+
s.base += r.base;
|
|
99
|
+
out.set(k, s);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function risingBuilder(rows, identities) {
|
|
105
|
+
const ranked = [...sumBy(rows, 'githubId')]
|
|
106
|
+
.filter(([, s]) => s.base < RISING_BASE_MAX && s.tasks_shipped >= 1)
|
|
107
|
+
.map(([id, s]) => ({ ...identities.get(id), credits: s.credits, tasks_shipped: s.tasks_shipped, karma: s.karma }))
|
|
108
|
+
.sort(compareRows);
|
|
109
|
+
if (!ranked.length) return null;
|
|
110
|
+
const { handle, github_login, display_name, avatar_url, credits, tasks_shipped } = ranked[0];
|
|
111
|
+
return { handle, github_login, display_name, avatar_url, credits, tasks_shipped };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const byText = (x, y) => (x < y ? -1 : x > y ? 1 : 0);
|
|
115
|
+
function projectsOnTheMove(rows, named) {
|
|
116
|
+
return [...sumBy(rows.filter((r) => named.has(r.clientId)), 'clientId')]
|
|
117
|
+
.filter(([, s]) => s.tasks_shipped >= 1)
|
|
118
|
+
.map(([id, s]) => ({ ...named.get(id), tasks_shipped: s.tasks_shipped, credits: s.credits }))
|
|
119
|
+
.sort((a, b) => (b.tasks_shipped - a.tasks_shipped) || (b.credits - a.credits)
|
|
120
|
+
|| byText(String(a.name), String(b.name)) || byText(String(a.origin), String(b.origin)))
|
|
121
|
+
.slice(0, PROJECTS_MAX);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// readMovers — { window: { start, end }, rising, projects }: the window as ISO instants,
|
|
125
|
+
// rising null when nobody qualifies or may be shown, projects best first (at most
|
|
126
|
+
// PROJECTS_MAX). `now` is injectable for tests.
|
|
127
|
+
async function readMovers({ now = new Date(), pool = defaultPool } = {}) {
|
|
128
|
+
const w = windowRows(now, pool);
|
|
129
|
+
const rows = await w.promise;
|
|
130
|
+
// Only a builder or project with a shipped work in the 30 days can be on either card.
|
|
131
|
+
const shipped = rows.filter((r) => r.tasks_shipped > 0);
|
|
132
|
+
const identities = await eligibleIdentities([...new Set(shipped.map((r) => r.githubId))], pool);
|
|
133
|
+
const shown = rows.filter((r) => identities.has(r.githubId));
|
|
134
|
+
const named = await namedProjects([...new Set(shown.filter((r) => r.tasks_shipped > 0).map((r) => r.clientId))], pool);
|
|
135
|
+
return {
|
|
136
|
+
window: { start: w.start.toISOString(), end: w.end.toISOString() },
|
|
137
|
+
rising: risingBuilder(shown, identities),
|
|
138
|
+
projects: projectsOnTheMove(shown, named),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function clearMoversMemo() {
|
|
143
|
+
rowsMemo.clear();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
readMovers,
|
|
148
|
+
clearMoversMemo,
|
|
149
|
+
RISING_BASE_MAX,
|
|
150
|
+
PROJECTS_MAX,
|
|
151
|
+
MOVERS_TTL_MS,
|
|
152
|
+
};
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
// It also serves GET /community/builder-of-the-week (spec D5, task 1002981), the
|
|
10
10
|
// board's weekly crown, from ../builder-of-the-week.js on the same terms, and GET
|
|
11
11
|
// /community/crowns (task 1004902), the week, month and year for the hub's /community
|
|
12
|
-
// page, from ../community-crowns.js
|
|
12
|
+
// page, from ../community-crowns.js, and GET /community/movers (task 1002983), the rising
|
|
13
|
+
// builder and the projects on the move under them, from ../community-movers.js.
|
|
13
14
|
//
|
|
14
15
|
// Reaches core only through the doorway (ADR 0083).
|
|
15
16
|
'use strict';
|
|
@@ -19,6 +20,7 @@ const api = require('../../../src/module-api');
|
|
|
19
20
|
const leaderboard = require('../community-leaderboard');
|
|
20
21
|
const builderOfTheWeek = require('../builder-of-the-week');
|
|
21
22
|
const communityCrowns = require('../community-crowns');
|
|
23
|
+
const communityMovers = require('../community-movers');
|
|
22
24
|
const { createReadRateLimit } = require('../read-rate-limit');
|
|
23
25
|
|
|
24
26
|
const { pool, corsPublicGet } = api;
|
|
@@ -31,6 +33,8 @@ const WEEK_READ_RATE_SCOPE = 'builder-of-the-week-read';
|
|
|
31
33
|
const weekReadRateLimit = createReadRateLimit({ scope: WEEK_READ_RATE_SCOPE, limit: READ_RATE_LIMIT });
|
|
32
34
|
const CROWNS_READ_RATE_SCOPE = 'community-crowns-read';
|
|
33
35
|
const crownsReadRateLimit = createReadRateLimit({ scope: CROWNS_READ_RATE_SCOPE, limit: READ_RATE_LIMIT });
|
|
36
|
+
const MOVERS_READ_RATE_SCOPE = 'community-movers-read';
|
|
37
|
+
const moversReadRateLimit = createReadRateLimit({ scope: MOVERS_READ_RATE_SCOPE, limit: READ_RATE_LIMIT });
|
|
34
38
|
|
|
35
39
|
module.exports = function communityLeaderboardRoutes() {
|
|
36
40
|
const router = express.Router();
|
|
@@ -109,7 +113,23 @@ module.exports = function communityLeaderboardRoutes() {
|
|
|
109
113
|
}
|
|
110
114
|
});
|
|
111
115
|
|
|
116
|
+
router.use('/community/movers', corsPublicGet('/community/movers'));
|
|
117
|
+
|
|
118
|
+
// rank: public — the rising builder and the projects on the move, the last 30 days (task
|
|
119
|
+
// 1002983). Takes no parameters and writes nothing. Viewer-independent; no-store so a
|
|
120
|
+
// builder who hides leaves it on the next read (ADR 0171 D5).
|
|
121
|
+
router.get('/community/movers', moversReadRateLimit, async (req, res) => {
|
|
122
|
+
try {
|
|
123
|
+
const out = await communityMovers.readMovers({ pool });
|
|
124
|
+
res.set('Cache-Control', 'no-store');
|
|
125
|
+
res.json(out);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
api.logger('platform-identity').error({ err: err && err.message }, 'GET /community/movers failed');
|
|
128
|
+
res.fail('community_movers_failed', { status: 500, message: 'internal error' });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
112
132
|
return router;
|
|
113
133
|
};
|
|
114
134
|
|
|
115
|
-
module.exports._internals = { READ_RATE_LIMIT, READ_RATE_SCOPE, WEEK_READ_RATE_SCOPE, CROWNS_READ_RATE_SCOPE };
|
|
135
|
+
module.exports._internals = { READ_RATE_LIMIT, READ_RATE_SCOPE, WEEK_READ_RATE_SCOPE, CROWNS_READ_RATE_SCOPE, MOVERS_READ_RATE_SCOPE };
|