feat(admin): view a user's shard footprint at /admin/users/:id
Add a "View" action beside Edit in the users table that opens a dedicated,
read-only page showing everything the uo-link shard knows about a user,
scoped to their linked game accounts: character rosters, currently-online
characters, houses (IDOC-first), and recent vendor sales.
Backend (admin-only, under the existing /users adminOnly gate):
- GET /admin/users/:id — single sanitized user (page is deep-linkable)
- GET /admin/users/:id/shard/{accounts,sales,houses,online}
- shardState: listHousesByAccounts / listOnlineByAccounts (+ model shapers)
- Extract salesForAccounts into utils/shardSales; reuse in player getSales
- Live rosters reuse the existing admin-bypass /admin/shard/* endpoints,
so no new routes for roster/vendors/char
Frontend:
- UserDetail page reusing CharacterStats / GameAccounts / VendorSales
- GameAccounts gains a readOnly prop (drops link form + self-voice copy)
- api.admin.getUser + api.admin.userShard(id) scope; route + layout title
Tests: adminUserShard.test.js (404, account scoping, empty accounts,
salesForAccounts cap/filter). Full server suite 164 pass; client builds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114TpmrNW4wNXsHq5CR72jQ
This commit is contained in:
@@ -136,9 +136,8 @@ async function upsertHouse(data) {
|
||||
await db.upsertHouse(data.serial, fields)
|
||||
}
|
||||
|
||||
async function listIdoc() {
|
||||
const rows = await db.listIdocHouses()
|
||||
return rows.map((r) => ({
|
||||
function shapeHouse(r) {
|
||||
return {
|
||||
serial: r.serial,
|
||||
stage: r.stage,
|
||||
map: r.map,
|
||||
@@ -153,7 +152,24 @@ async function listIdoc() {
|
||||
lastRefreshed: r.last_refreshed,
|
||||
isIdoc: Boolean(r.is_idoc),
|
||||
updatedAt: r.updated_at,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async function listIdoc() {
|
||||
const rows = await db.listIdocHouses()
|
||||
return rows.map(shapeHouse)
|
||||
}
|
||||
|
||||
// Houses owned by the given game accounts (admin: a user's linked accounts).
|
||||
async function listHousesForAccounts(accounts) {
|
||||
const rows = await db.listHousesByAccounts(accounts)
|
||||
return rows.map(shapeHouse)
|
||||
}
|
||||
|
||||
// Online players on the given game accounts (admin: a user's linked accounts).
|
||||
async function listOnlineForAccounts(accounts) {
|
||||
const rows = await db.listOnlineByAccounts(accounts)
|
||||
return rows.map(shapeOnline)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@@ -163,9 +179,11 @@ module.exports = {
|
||||
onlineCount,
|
||||
listOnline,
|
||||
listOnlineLinked,
|
||||
listOnlineForAccounts,
|
||||
addEconomySample,
|
||||
listEconomy,
|
||||
latestEconomy,
|
||||
upsertHouse,
|
||||
listIdoc,
|
||||
listHousesForAccounts,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user