Files
Module-Rust/server/router/admin/visibility.router.js
wtclaude 549b3a5ffd
All checks were successful
PR Checks / client-build (pull_request) Successful in 25s
PR Checks / server-tests (pull_request) Successful in 31s
PR Checks / frozen-manifest (pull_request) Successful in 44s
feat(rust): Dynamic PVP's zones on the feed and the map (PLAN_REDESIGNS §14.3.3)
Stage 4 of the §14.3 integration plan, on protocol 14 (docs
rust-link/PROTOCOL.md §20.7).

- pvp.zone.created is a public kind, pvp.zone.deleted a staff kind. Each row
  is read per viewer by the new dynamicPvp.js: grid for everyone, the event's
  name only for Dynamic PVP's own vanilla events, position and zone id for
  staff only.
- The feed's world filter carries the opening: "A PvP zone opened around the
  downed patrol helicopter · G7 · for 10 min". It names nobody.
- The map gains a fifth layer, `pvp` (PvP zones), public by default with its
  own audience in Admin → Rust visibility, drawn as a red ring with its
  event's name.
- Server settings' plugin line names Dynamic PVP; the swagger schemas and
  route descriptions carry the layer and the integration (fragment
  regenerated); ci/bundle.json ships dynamicPvp.js; README lists it.

Tests: server 611/611 (dynamicPvp.test.js new, catalogue and map extended)
and every check; client 89/89 (feed and map tooltip), build and externals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-15 06:12:07 -05:00

57 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ── Admin · Rust · Visibility ─────────────────────────────────────────────
//
// Mounted under the admin tier's `/rust` prefix, so every path here is
// `/api/v1/admin/rust/visibility`. Who may see what the servers say about the
// people on them — a fourth subject beside the bridge, the permissions and the
// mod configuration.
//
// **Every route is `requireRole('admin')`.** The tier's own gate admits editors
// and moderators, and a moderator widening the roll call to the public is the
// decision the org lead settled should be deliberate. Reading is gated the same
// as writing: the screen is one form, and a view of the settings without the
// power to change them is not something anybody has asked for.
const core = require('../../core')
const express = core.express
const visibility = require('./visibility.controller')
const { requireRole, validate } = core.middleware
const { body } = core.validator
const visibilityRouter = express.Router()
const AUDIENCES = ['staff', 'signed_in', 'public']
const CLAN_AUDIENCES = ['members', 'signed_in', 'public']
visibilityRouter.get(
'/',
// #swagger.tags = ['Admin · Rust']
// #swagger.summary = 'Who may see who is online, and who may see a clan roster'
// #swagger.description = 'The presence fleet default and every server’s optional override. It governs the Online list, every feed item that names a player who was on the server (connects, respawns, deaths, chat, tallies) and the leaderboard’s `lastSeen`. The default is `staff`: nothing names who is online until an operator widens it. The player count is public at every setting. `clans` carries the clan roster audience (default `members`: the clan’s own linked members, and staff) and each server’s clan board — whether it is current, at the game’s 100-clan ceiling, or running the uMod Clans plugin, whose clans are not Teams. `map` carries the live map’s switches: each layer’s fleet audience (`world`, `events` and `pvp` public, `players` and `bases` staff by default), the own-and-mates switch (on), the monument-label switches (`minorLabels` start hidden, every other label is drawn; `markerLabels` lists each server’s current map’s labels), each server’s overrides, and what each server’s map picture is — its source, the map it is of, when it was fetched, and how long a render would stall that server.'
/* #swagger.responses[200] = { description: 'The fleet default and each server', content: { "application/json": { schema: { $ref: "#/components/schemas/RustVisibility" } } } } */
requireRole('admin'),
visibility.read,
)
visibilityRouter.put(
'/',
// #swagger.tags = ['Admin · Rust']
// #swagger.summary = 'Change who may see who is online, who may see a clan roster, or which servers say news in chat'
// #swagger.description = 'Sets the presence fleet default, one or more server overrides, the clan roster audience, the per-server news-in-chat switches, or any of them together. A server set to `null` follows the fleet default again. `news` maps a server id to `true` or `false`: whether a published news post is also said in the in-game chat of that server (off by default). `newsDelivery` maps a server id to `chat` or `popup`: where that post goes — a popup needs PopupNotifications on the server, and one without it refuses the post with a reason. Validated whole before anything is written: a request naming a server that does not exist changes nothing. Widening the clan roster audience also shows which members are online to that audience, because a roster row carries it. `map` is `{ fleet, servers }`: `fleet` maps a layer (`world`, `events`, `players`, `bases`, `pvp`) to an audience and `mates` to true or false; `servers` maps a server id to the same shape, where null follows the fleet. `markers`, in either, maps a monument label key (lower case) to true or false, and null clears it (D196). The players layer never shows more than who may see who is online, whatever it is set to.'
/* #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/RustVisibilityUpdate" } } } } */
/* #swagger.responses[200] = { description: 'Saved; answers the new state', content: { "application/json": { schema: { $ref: "#/components/schemas/RustVisibility" } } } } */
/* #swagger.responses[400] = { description: 'An audience that does not exist' } */
/* #swagger.responses[404] = { description: 'A server that does not exist' } */
requireRole('admin'),
body('fleet').optional().isIn(AUDIENCES).withMessage(`fleet must be one of ${AUDIENCES.join(', ')}`),
body('servers').optional().isObject().withMessage('servers maps a server id to an audience or null'),
body('clanRoster').optional().isIn(CLAN_AUDIENCES).withMessage(`clanRoster must be one of ${CLAN_AUDIENCES.join(', ')}`),
body('news').optional().isObject().withMessage('news maps a server id to true or false'),
body('newsDelivery').optional().isObject().withMessage('newsDelivery maps a server id to chat or popup'),
body('map').optional().isObject().withMessage('map carries fleet and servers, each an object of layer switches'),
validate,
visibility.update,
)
module.exports = visibilityRouter