feat(rust): the rewards — tally, kit reward, chat and the news leg (phase 13b, protocol 10)
Four event verbs and the announce leg, per PLAN.md §29: - rust.participation.open / .collect: the plugin counts who takes part (seconds, kills or both, in a zone this run opened or the whole server) and collect files them as the run's participants, keyed by Steam id. - rust.kit.entitle: the five recipient modes (D101), rows in the new rust_perm_run_grants (D84) unioned into the permission push, one extra use of the kit per reward as site-held credits on perm.sync (D103), and the rust.kit.entitled notice deferred from phase 10 (D64). - rust.announce: one server or every server (D105). - rust.chat announce leg, speaking only on servers whose new news switch is on (D104) - a card on Admin -> Rust visibility (D106). Budgets rust.grants and rust.announcements; the kit source and four fixed-choice sources (core has no enum param type). rust_perm_run_grants carries core's idempotency key so a revert of a lost answer can find its rows. Protocol 10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -184,6 +184,12 @@ async function describe() {
|
||||
}
|
||||
}),
|
||||
},
|
||||
// D104/D106: whether a published news post is said in each server's chat.
|
||||
// On this page because it is the one that lists every server with a setting
|
||||
// of its own, and it answers the same kind of question — what a server shows.
|
||||
news: {
|
||||
servers: servers.map((s) => ({ id: s.id, name: s.name, enabled: Boolean(s.enabled), on: Boolean(Number(s.announceNews)) })),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +203,7 @@ async function describe() {
|
||||
* Resolves `{ ok, changed }`, or `{ ok: false, status, message }` — a refusal is a
|
||||
* sentence the page can show.
|
||||
*/
|
||||
async function update({ fleet, servers, clanRoster } = {}, actor = null) {
|
||||
async function update({ fleet, servers, clanRoster, news } = {}, actor = null) {
|
||||
if (fleet !== undefined && !isAudience(fleet)) {
|
||||
return { ok: false, status: 400, message: `"${fleet}" is not an audience. Choose one of: ${AUDIENCES.join(', ')}.` }
|
||||
}
|
||||
@@ -221,6 +227,17 @@ async function update({ fleet, servers, clanRoster } = {}, actor = null) {
|
||||
}
|
||||
}
|
||||
|
||||
const newsChanges = Object.entries(news || {})
|
||||
for (const [id, value] of newsChanges) {
|
||||
if (typeof value !== 'boolean') {
|
||||
return { ok: false, status: 400, message: `News in game chat is on or off for server ${id}, not "${value}".` }
|
||||
}
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
if ((await db.getServerPresence(id)) === undefined) {
|
||||
return { ok: false, status: 404, message: `There is no server called ${id}.` }
|
||||
}
|
||||
}
|
||||
|
||||
const userId = actor && actor.id != null ? actor.id : null
|
||||
|
||||
if (fleet !== undefined) await db.setSetting(PRESENCE_KEY, fleet, userId)
|
||||
@@ -229,6 +246,10 @@ async function update({ fleet, servers, clanRoster } = {}, actor = null) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await db.setServerPresence(id, value)
|
||||
}
|
||||
for (const [id, on] of newsChanges) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await db.setServerNews(id, on)
|
||||
}
|
||||
|
||||
// What was written, for the controller's audit row. Recorded there rather than
|
||||
// here because the activity log takes the REQUEST (who, from where), and a
|
||||
@@ -239,6 +260,7 @@ async function update({ fleet, servers, clanRoster } = {}, actor = null) {
|
||||
...(fleet !== undefined ? { fleet } : {}),
|
||||
...(clanRoster !== undefined ? { clanRoster } : {}),
|
||||
servers: Object.fromEntries(changes.map(([id, value]) => [id, value === null ? 'inherit' : value])),
|
||||
...(newsChanges.length ? { news: Object.fromEntries(newsChanges) } : {}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user