Let staff link their own characters + share the game-accounts UI

- 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
This commit is contained in:
2026-07-11 03:05:43 -05:00
parent 49ce230c3a
commit 74d2ead958
9 changed files with 473 additions and 159 deletions

View File

@@ -1886,6 +1886,207 @@
]
}
},
"/api/v1/admin/shard/link": {
"post": {
"tags": [
"Admin · Account"
],
"summary": "Link an in-game account with a one-time code (self)",
"description": "",
"responses": {
"200": {
"description": "Linked",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShardLinkResult"
}
}
}
},
"400": {
"description": "Unknown or expired code",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"502": {
"description": "Bad Gateway"
},
"503": {
"description": "Service Unavailable"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShardLinkRequest"
}
}
}
}
}
},
"/api/v1/admin/shard/accounts": {
"get": {
"tags": [
"Admin · Account"
],
"summary": "List the callers linked game accounts (self)",
"description": "",
"responses": {
"200": {
"description": "Linked accounts",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardLink"
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/shard/roster/{account}": {
"get": {
"tags": [
"Admin · Account"
],
"summary": "Character roster for a linked account (self)",
"description": "",
"parameters": [
{
"name": "account",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "A game account linked to the caller."
}
],
"responses": {
"200": {
"description": "Account roster",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"403": {
"description": "Account not linked to the caller",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/shard/vendors/{account}": {
"get": {
"tags": [
"Admin · Account"
],
"summary": "Player vendors for a linked account (self)",
"description": "",
"parameters": [
{
"name": "account",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "A game account linked to the caller."
}
],
"responses": {
"200": {
"description": "Vendor snapshot",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"403": {
"description": "Account not linked to the caller",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/dashboard": {
"get": {
"tags": [