uo-link: staff-only public presence + admin character access #49
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/uo-link-sidecar"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Completes the uo-link sidecar feature and tightens who can see what.
Access-control changes
/public/shard/onlinepreviously listed any linked website user (includingplayeraccounts) with their name and map location.listOnlineLinkednow joins through tousersand filters to staff roles (admin/editor/moderator); the public section is relabeled "Staff online". Location is still shown; account/vitals are still never sent.isAdmin(req)), so it only ever widens access for genuine admins —/player/shard/*callers are roleplayerand never hit it.Also included
VendorSalescomponent, removal of the old publicShardCharroute.swagger-output.json(new summaries reflect the staff-only online list and admin "any character" access).[linkflow, and what the public/player/admin surfaces each expose. Plus an intro bullet, contents entry, and the shard endpoint groups in the API table.Verification
clientbuilds clean (vite build, 229 modules).npm run swaggerregenerated successfully.Not yet done: live-DB / OAuth smoke test against a running shard + DB.
🤖 Generated with Claude Code
Introduces the DB-backed connection config for the uo-link sidecar (the HTTP + WebSocket bridge to the ServUO shard) and a never-throw REST client, mirroring the existing Discord-bot integration: - uo_link_config singleton table (base/ws URL, AES-256-GCM-encrypted shared token, protocol pin, enabled, and last-known status/plugin_connected/ last_event_at/boot_id mirrors for the admin panel). - model/uoLinkConfig: getSafe (never returns the token — only hasToken), getWithToken (server-side decrypt), save (blank token = unchanged), recordStatus (mirror the sidecar's reported state). - utils/uoLinkClient: never-throw fetch client returning {ok,data,status, error}; Bearer token + X-UOLink-Version on every call; brief config cache; helpers for health/char/roster/vendors/history/economy/link/towncrier. - .env.example: UOLINK_BASE_URL/WS_URL/PROTOCOL defaults (token stays admin-managed in the DB, never an env var). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qPmpmVH1xGCiZoz9m9vW3Curated, same-origin, token-free reads so the browser never sees the sidecar URL or token: - public/shard.controller.js: - GET /public/shard/status — connection state + online count + latest economy (from the site's ingested data). - GET /public/shard/feed?kind=&limit= — recent notable events from the log. - GET /public/shard/economy — gold-supply series (oldest → newest). - GET /public/shard/idoc — houses currently at IDOC. - GET /public/shard/char/:serial — live sheet round-trip via uoLinkClient, briefly cached; 503 (shard restarting) serves a stale cache or a retry banner rather than an error. - GET /public/shard/stream — public SSE channel (safe kinds only). - Wired into public.routes.js with express-validator guards and #swagger annotations; new "Public · Shard" tag + ShardStatus/ShardEvent/ ShardEconomyPoint/ShardHouse schemas; swagger-output.json regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qPmpmVH1xGCiZoz9m9vW3Ties an in-game account to a website user and gates reads on ownership. - schema: shard_account_links (account PK → user_id, char_name, linked_at; FK users ON DELETE CASCADE) — the site-side mirror of the sidecar's authoritative link. - model/shardLinks: upsert/list/ownership-check/getByAccount/unlink. - player/shard.controller.js: - POST /player/shard/link — confirm a one-time [link code via uoLinkClient.confirmLink(code, req.user.id); on link.ok mirror the link and activity.log it; bad/expired codes → 400, shard down → 503. - GET /player/shard/accounts — the caller's linked accounts. - GET /player/shard/roster/:account and /vendors/:account — live round-trips, ownership-checked against the mirror (403 otherwise), 503 on shard restart. - player.routes.js: mounted under the existing requireRole('player') gate with express-validator guards + #swagger annotations; new "Player · Shard" tag and ShardLinkRequest/ShardLinkResult/ShardLink schemas; spec regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qPmpmVH1xGCiZoz9m9vW3- Backend: /admin/shard/{link,accounts,roster/:account,vendors/:account} — staff self-service, reusing the player/shard controller (it keys off req.user.id, so the same handlers serve any logged-in role). Swagger under Admin · Account; spec regenerated. - components/GameAccounts.jsx: the link-prompt + character-roster UI extracted into one reusable component parametrized by an api scope and a charTo(serial) route builder. - PlayerCharacters now renders it (player scope → /player/char/:serial). - Admin: "My Characters" nav item + /admin/characters (AdminCharacters) and /admin/characters/:serial (AdminCharacter, in-shell sheet), using the admin self-service scope. api.admin.shard.* added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qPmpmVH1xGCiZoz9m9vW3