feat(rust): Backpacks — staff and player views, the Bigger backpack step (PLAN_REDESIGNS §11.3)
The site half of §11.3.2, on protocol 14:
- `rust.backpack.size` ("Bigger backpack"): a kit reward with a different
permission (D337) — one rust_perm_run_grants row per website user with
backpacks.size.N, no kit, no credit; the perm mirror pushes it and the run's
revert deletes it. risk change, reversible ledger, a new `rust.backpacks`
budget, rust.kit.entitle's recipient modes including `linked`. Refused with
"Backpacks is not installed on this server" in the dry run too (D199), and a
size the server does not offer is refused by name. Its message is never
silent: empty is the take-back warning (D338).
- `rust.options.backpacksizes`: each server's sizes above its smallest, from
the last hello, grouped by server.
- GET /player/rust/backpacks (own links only, 20 reads a minute) and
GET /admin/users/:id/rust/backpacks (that user's links only). Live, never
stored; a stranger's id is a 404 and never reaches the game.
- Client: a shared Backpack panel (pick server and account, open, read once)
on the account page and the admin user page; the grid draws pages of
pageSize, each slot named by the game (D340, no icons), blueprints by what
they make (D341), overflow marked. Backpacks joins the server's mod line.
Server 548/548 (13 new in test/backpacks.test.js), client 79/79 (5 new),
routes.manifest regenerated against the pinned core (89 routes), swagger
fragment regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
|
||||
const core = require('../../core')
|
||||
|
||||
const backpacks = require('../../model/backpacks/backpacks.model')
|
||||
const economics = require('../../model/economics/economics.model')
|
||||
const links = require('../../model/links/links.model')
|
||||
const npcs = require('../../model/npcs/npcs.model')
|
||||
@@ -70,6 +71,28 @@ async function ownEconomics(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /player/rust/backpacks — one of the caller's own backpacks (§11.3.2),
|
||||
* read live. Only an account the caller has linked: anybody else's is a 404,
|
||||
* the same answer as an id nobody holds, so the route says nothing about
|
||||
* whose ids exist.
|
||||
*/
|
||||
async function ownBackpack(req, res) {
|
||||
const { serverId, steamId } = req.query
|
||||
try {
|
||||
const held = await links.listForUser(req.user.id)
|
||||
if (!held.some((l) => String(l.steamId) === String(steamId))) {
|
||||
return res.status(404).json({ message: 'That Steam account is not linked to you' })
|
||||
}
|
||||
const result = await backpacks.read({ serverId, steamId })
|
||||
if (!result.ok) return res.status(result.status).json({ message: result.message, ...(result.reason ? { reason: result.reason } : {}) })
|
||||
return res.json(result.backpack)
|
||||
} catch (err) {
|
||||
log.error('failed to read a player’s backpack', { serverId, error: err.message })
|
||||
return res.status(500).json({ message: 'Failed to read your backpack' })
|
||||
}
|
||||
}
|
||||
|
||||
/** GET /player/rust/links — the Steam accounts the caller holds. */
|
||||
async function listLinks(req, res) {
|
||||
try {
|
||||
@@ -202,4 +225,4 @@ async function listPermissions(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { listServers, listLinks, confirmLink, removeLink, listPermissions, ownNpcKills, ownEconomics }
|
||||
module.exports = { listServers, listLinks, confirmLink, removeLink, listPermissions, ownNpcKills, ownEconomics, ownBackpack }
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
const core = require('../../core')
|
||||
|
||||
const express = core.express
|
||||
const { body, param } = core.validator
|
||||
const { body, param, query } = core.validator
|
||||
|
||||
const rust = require('./rust.controller')
|
||||
const { validate, rateLimit } = core.middleware
|
||||
@@ -67,6 +67,37 @@ playerRustRouter.get(
|
||||
rust.ownNpcKills,
|
||||
)
|
||||
|
||||
/**
|
||||
* A backpack read runs on the game's main thread (55–117 ms the first time
|
||||
* after a boot, §11.3.1), so a page held open on refresh is not allowed to
|
||||
* become a load on the server. Generous for a person, tight for a loop.
|
||||
*/
|
||||
const backpackLimiter = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 20,
|
||||
label: 'rust-backpack-read',
|
||||
message: 'Too many backpack reads. Please wait a minute.',
|
||||
})
|
||||
|
||||
playerRustRouter.get(
|
||||
'/backpacks',
|
||||
// #swagger.tags = ['Player · Rust']
|
||||
// #swagger.summary = 'Your backpack on one server'
|
||||
// #swagger.description = 'One of the caller’s own linked Steam accounts’ Backpacks backpack on one server, read live from the game and never stored (PLAN_REDESIGNS §11.3.2). `exists: false` for an account that has never had one. Each stack has its slot, shortname, the game’s English name (`display`), amount, and skin, custom name, blueprint target and nested contents where set; `overflow` marks a stack past the capacity, which moves to the owner’s inventory the next time they open the backpack. 404 for a Steam account the caller has not linked; 409 where the server has no Backpacks. Requires a session; 20 reads a minute.'
|
||||
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
|
||||
// #swagger.parameters['serverId'] = { in: 'query', required: true, schema: { type: 'string' }, description: 'The server.' }
|
||||
// #swagger.parameters['steamId'] = { in: 'query', required: true, schema: { type: 'string' }, description: 'One of the caller’s linked Steam ids.' }
|
||||
/* #swagger.responses[200] = { description: 'The backpack', content: { "application/json": { schema: { $ref: "#/components/schemas/RustBackpack" } } } } */
|
||||
/* #swagger.responses[404] = { description: 'No such server, or not one of your linked Steam accounts', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
|
||||
/* #swagger.responses[409] = { description: 'Backpacks is not installed on that server', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
|
||||
/* #swagger.responses[503] = { description: 'The server could not be reached', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
|
||||
backpackLimiter,
|
||||
query('serverId').isString().isLength({ min: 1, max: 64 }),
|
||||
query('steamId').isString().matches(STEAM_ID_RE),
|
||||
validate,
|
||||
rust.ownBackpack,
|
||||
)
|
||||
|
||||
playerRustRouter.get(
|
||||
'/economics',
|
||||
// #swagger.tags = ['Player · Rust']
|
||||
|
||||
Reference in New Issue
Block a user