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:
@@ -28,6 +28,7 @@ const GROUPS = 'rust_perm_groups'
|
||||
const GROUP_PERMISSIONS = 'rust_perm_group_permissions'
|
||||
const GROUP_MEMBERS = 'rust_perm_group_members'
|
||||
const GRANTS = 'rust_perm_grants'
|
||||
const RUN_GRANTS = 'rust_perm_run_grants'
|
||||
const PUSHED = 'rust_perm_pushed'
|
||||
const DRIFT = 'rust_perm_drift'
|
||||
const REVOCATIONS = 'rust_perm_revocations'
|
||||
@@ -190,6 +191,76 @@ async function deleteGrant(id) {
|
||||
return Number(result.affectedRows || 0) > 0
|
||||
}
|
||||
|
||||
// ---- what events granted (phase 13b) ----
|
||||
//
|
||||
// `rust_perm_run_grants` is authored by `rust.kit.entitle`, never by a person,
|
||||
// and it is read beside `rust_perm_grants` rather than merged into it (D84): the
|
||||
// push unions the two, and a revert deletes exactly one step's rows.
|
||||
|
||||
/** Every event grant, for the push. Small: one row per recipient per reward step still standing. */
|
||||
async function listRunGrants() {
|
||||
return core.query(
|
||||
`SELECT run_id AS runId, step_id AS stepId, user_id AS userId, server_id AS serverId,
|
||||
steam_id AS steamId, permission, kit, credit
|
||||
FROM ${RUN_GRANTS}`,
|
||||
)
|
||||
}
|
||||
|
||||
/** One step's rows. A repeated key finds them here and writes nothing new. */
|
||||
async function listRunGrantsForStep(runId, stepId) {
|
||||
return core.query(
|
||||
`SELECT user_id AS userId, server_id AS serverId, steam_id AS steamId, permission, kit, credit
|
||||
FROM ${RUN_GRANTS}
|
||||
WHERE run_id = ? AND step_id = ?`,
|
||||
[String(runId), String(stepId)],
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* One step's recipients, in one statement. `INSERT IGNORE` against the unique
|
||||
* key, so a retry that races the first attempt writes each row once.
|
||||
*/
|
||||
async function insertRunGrants(rows) {
|
||||
if (!rows.length) return 0
|
||||
|
||||
const result = await core.query(
|
||||
`INSERT IGNORE INTO ${RUN_GRANTS} (run_id, step_id, idem_key, user_id, server_id, steam_id, permission, kit, credit)
|
||||
VALUES ${placeholders(rows, 9)}`,
|
||||
rows.flatMap((r) => [
|
||||
String(r.runId),
|
||||
String(r.stepId),
|
||||
String(r.idemKey || ''),
|
||||
r.userId,
|
||||
r.serverId,
|
||||
r.steamId,
|
||||
r.permission || '',
|
||||
r.kit,
|
||||
r.credit ? 1 : 0,
|
||||
]),
|
||||
)
|
||||
|
||||
return Number(result.affectedRows || 0)
|
||||
}
|
||||
|
||||
/** Withdraw one step's rows. Returns the servers they were on; none is a success. */
|
||||
async function deleteRunGrantsForStep(runId, stepId) {
|
||||
return deleteRunGrantsWhere('run_id = ? AND step_id = ?', [String(runId), String(stepId)])
|
||||
}
|
||||
|
||||
/** The same, found by core's idempotency key — the revert of an answer core lost. */
|
||||
async function deleteRunGrantsForKey(runId, idemKey) {
|
||||
if (!idemKey) return []
|
||||
return deleteRunGrantsWhere('run_id = ? AND idem_key = ?', [String(runId), String(idemKey)])
|
||||
}
|
||||
|
||||
async function deleteRunGrantsWhere(where, params) {
|
||||
const found = await core.query(`SELECT DISTINCT server_id AS serverId FROM ${RUN_GRANTS} WHERE ${where}`, params)
|
||||
if (!found.length) return []
|
||||
|
||||
await core.query(`DELETE FROM ${RUN_GRANTS} WHERE ${where}`, params)
|
||||
return found.map((row) => row.serverId)
|
||||
}
|
||||
|
||||
/**
|
||||
* One website account by name, for the authoring form.
|
||||
*
|
||||
@@ -463,6 +534,7 @@ async function listCatalogue() {
|
||||
module.exports = {
|
||||
GROUPS,
|
||||
GRANTS,
|
||||
RUN_GRANTS,
|
||||
PUSHED,
|
||||
DRIFT,
|
||||
listGroups,
|
||||
@@ -478,6 +550,11 @@ module.exports = {
|
||||
getGrant,
|
||||
insertGrant,
|
||||
deleteGrant,
|
||||
listRunGrants,
|
||||
listRunGrantsForStep,
|
||||
insertRunGrants,
|
||||
deleteRunGrantsForStep,
|
||||
deleteRunGrantsForKey,
|
||||
findUserByUsername,
|
||||
listLinks,
|
||||
listGroupsForUser,
|
||||
|
||||
Reference in New Issue
Block a user