Restrict public presence to staff + let admins view any character

Public "Online now" now lists only players whose game account is linked
to a STAFF website user (admin/editor/moderator) — linked players are no
longer exposed publicly with their name and location. listOnlineLinked
joins through to users and filters on role; the section is relabeled
"Staff online".

Character/roster/vendor reads gain an admin bypass: admins may view any
character's data, while players (and editor/moderator staff) stay limited
to accounts they have personally linked. The bypass lives in the shared
player controller and only ever widens access for genuine admins.

Also finalizes the uo-link character/vendor front end (player + admin
character sheets, VendorSales component, ShardChar removed) and
regenerates swagger-output.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kj5s1QCKobuFPYmqxjy1q
This commit is contained in:
2026-07-11 09:15:18 -05:00
parent 49d0c1bd11
commit c4245e3f6a
20 changed files with 643 additions and 216 deletions

View File

@@ -138,7 +138,7 @@ adminRouter.get(
adminRouter.get(
'/shard/roster/:account',
// #swagger.tags = ['Admin · Account']
// #swagger.summary = 'Character roster for a linked account (self)'
// #swagger.summary = 'Character roster for an account (self; admins: any account)'
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
// #swagger.parameters['account'] = { in: 'path', required: true, schema: { type: 'string' }, description: 'A game account linked to the caller.' }
/* #swagger.responses[200] = { description: 'Account roster', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */
@@ -150,7 +150,7 @@ adminRouter.get(
adminRouter.get(
'/shard/vendors/:account',
// #swagger.tags = ['Admin · Account']
// #swagger.summary = 'Player vendors for a linked account (self)'
// #swagger.summary = 'Player vendors for an account (self; admins: any account)'
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
// #swagger.parameters['account'] = { in: 'path', required: true, schema: { type: 'string' }, description: 'A game account linked to the caller.' }
/* #swagger.responses[200] = { description: 'Vendor snapshot', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */
@@ -159,6 +159,26 @@ adminRouter.get(
validate,
selfShard.vendors,
)
adminRouter.get(
'/shard/char/:serial',
// #swagger.tags = ['Admin · Account']
// #swagger.summary = 'Character sheet (self-linked characters; admins: any character)'
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
// #swagger.parameters['serial'] = { in: 'path', required: true, schema: { type: 'string' }, description: 'Mobile serial, e.g. 0x24C.' }
/* #swagger.responses[200] = { description: 'Character profile', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */
/* #swagger.responses[403] = { description: 'Character not on an account linked to the caller', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
param('serial').matches(/^0x[0-9a-fA-F]+$/),
validate,
selfShard.getChar,
)
adminRouter.get(
'/shard/sales',
// #swagger.tags = ['Admin · Account']
// #swagger.summary = 'Recent player-vendor sales for the callers linked accounts (self)'
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
/* #swagger.responses[200] = { description: 'Vendor sales', content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/ShardVendorSale" } } } } } */
selfShard.getSales,
)
// ── Image uploads (screenshots/gallery) ───────────────────────────────
const UPLOAD_DIR =