feat(rust): what the site has given a player, as the player reads it
Phase 8's website half. Phase 7 made the site the author of in-game
privilege and gave an operator every view of it; this is the other side,
and it is the first time a player can see what they hold without asking
one.
`GET /player/rust/permissions` is self-scoped in SQL and read-only by
construction — a grant a player could change would not be a grant. Three
things make it a different shape from the admin read rather than a
filtered one:
* the scope arithmetic is answered on the server. A client handed `*`
would have to know what the fleet is to say anything, and then
`inScope` exists twice. Each entry carries the servers it reaches,
already resolved and already marked.
* `live` is the pushed ledger, never the authored row. A grant is not a
privilege in a game until a sync confirmed it, and phase 7 is careful
never to record a push that silently did nothing — so "waiting" is
honest, and the alternative is the site claiming to have given
something it has not.
* nothing says WHY it is waiting. An offline server, a permission no
loaded plugin registered and a store that has never seen the account
all look the same from here; telling them apart is an operator's
diagnosis and an inventory of what is installed.
An entitlement that reaches nobody still lists, and the page says so —
authored against the website account, it exists before a Steam id does,
and hiding it until one turns up is the defect the admin user page
shipped in phase 7 (PLAN.md §20.5).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
const core = require('../../core')
|
||||
|
||||
const links = require('../../model/links/links.model')
|
||||
const permissions = require('../../model/permissions/permissions.model')
|
||||
const servers = require('../../model/servers/servers.model')
|
||||
|
||||
const log = core.logger('player')
|
||||
@@ -134,4 +135,37 @@ async function removeLink(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { listServers, listLinks, confirmLink, removeLink }
|
||||
/**
|
||||
* GET /player/rust/permissions — what the site has given this player in game.
|
||||
*
|
||||
* Phase 7 made the website the author of in-game privilege and gave an operator
|
||||
* every view of it; this is the other side of that, and it is the first time a
|
||||
* player can see what they hold without asking one. Read-only by construction:
|
||||
* nothing a player can do here changes a grant, because a grant they could
|
||||
* change would not be a grant.
|
||||
*
|
||||
* The caller's Steam ids come from the link model rather than the permission
|
||||
* one, so the two questions stay in the files that own them — and the pushed
|
||||
* ledger is keyed by Steam id, which is the whole reason this read needs them.
|
||||
*/
|
||||
async function listPermissions(req, res) {
|
||||
try {
|
||||
const [accounts, serverRows] = await Promise.all([
|
||||
links.listForUser(req.user.id),
|
||||
servers.listPublic(),
|
||||
])
|
||||
|
||||
const held = await permissions.forPlayer(
|
||||
req.user.id,
|
||||
accounts.map((account) => account.steamId),
|
||||
serverRows,
|
||||
)
|
||||
|
||||
res.json({ ...held, accounts: accounts.length })
|
||||
} catch (err) {
|
||||
log.error('failed to read a player’s entitlements', { error: err.message })
|
||||
res.status(500).json({ message: 'Failed to read what you hold in game' })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { listServers, listLinks, confirmLink, removeLink, listPermissions }
|
||||
|
||||
Reference in New Issue
Block a user