fix(server): own game-account signup, and repair the gate slice 1 broke
`POST /player/shard/account` and its staff twin have answered 500 for every caller since slice 1: the ported controller called `settings.isGameAccountSignupEnabled()`, which is a member of core's settings model and not of `ctx.settings` — three functions, deliberately. The call was `undefined(...)`, the TypeError landed in the catch, and no test reached the branch. The gate now lives on the side that uses it (`utils/gameSignup.js`), which is also where the policy belongs: the setting's own help text names Bridge.cfg and says the shard's SignupMode must agree, and core cannot own a sentence about a UO shard. The admin field moves to this module's Shard page and the derived flag onto `/public/shard/features`, beside the visibility flags the same callers already read. The setting KEY is unchanged. Renaming `game_account_signup` would silently reset every configured instance to `disabled` on upgrade, with players reporting broken signup as the only clue — the same grandfathering as `spawn_atlas_servuo_path` and the seven stream ids. Both regression tests were shown to fail against the bug before it was fixed. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
63
server/utils/gameSignup.js
Normal file
63
server/utils/gameSignup.js
Normal file
@@ -0,0 +1,63 @@
|
||||
// ── Whether this site creates game accounts, and in which direction ────────
|
||||
//
|
||||
// This policy was core's until slice 3 of the Phase 3 extraction, and it should
|
||||
// never have been: the setting's own help text names *Bridge.cfg* and says the
|
||||
// game server's `SignupMode` must agree with it. That is a sentence about a UO
|
||||
// shard, and core cannot own a sentence about a UO shard.
|
||||
//
|
||||
// **The setting key is unchanged.** `game_account_signup` keeps its name and its
|
||||
// row in core's `settings` table, read and written through `ctx.settings`. The
|
||||
// key is not prefixed because renaming it would silently reset every existing
|
||||
// instance's configured mode to the default — the same reasoning that
|
||||
// grandfathered `spawn_atlas_servuo_path`, `cliloc_client_path` and the seven
|
||||
// stream ids (MODULE_API.md §6.5). A module owning an unprefixed settings key is
|
||||
// a grandfathering, not a pattern to copy.
|
||||
//
|
||||
// **It was also broken.** Slice 1 ported the call site
|
||||
// (`router/player/shard.controller.js`) still calling
|
||||
// `settings.isGameAccountSignupEnabled()`, which `ctx.settings` does not expose —
|
||||
// it is three functions, not the model. So `POST /player/shard/account` threw a
|
||||
// TypeError and answered 500 for every caller, and no test saw it because the
|
||||
// module's suite never reached that branch. This file is where that function now
|
||||
// lives, on the side that actually uses it.
|
||||
|
||||
const { settings } = require('../core')
|
||||
|
||||
const KEY = 'game_account_signup'
|
||||
|
||||
/**
|
||||
* The four modes, and what each means.
|
||||
*
|
||||
* `website` and `hybrid` are the two that accept a site-created account; `game`
|
||||
* means accounts are made in the client and only linked here. The shard's own
|
||||
* `SignupMode` still has the final say when the call is actually made — this is
|
||||
* the site half of an agreement between two systems, which is exactly why it
|
||||
* reads as UO policy rather than as site configuration.
|
||||
*/
|
||||
const MODES = ['disabled', 'website', 'hybrid', 'game']
|
||||
const OFFERS_SIGNUP = ['website', 'hybrid']
|
||||
|
||||
/** The configured mode, or `disabled` for anything unset or unrecognised. */
|
||||
async function getMode() {
|
||||
const value = await settings.get(KEY)
|
||||
return MODES.includes(value) ? value : 'disabled'
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this site offer game-account creation right now?
|
||||
*
|
||||
* Fails CLOSED on an unreadable setting, because `getMode` resolves an unknown
|
||||
* value to `disabled`. Offering a form that the shard will refuse is a dead end
|
||||
* a player cannot distinguish from a bug.
|
||||
*/
|
||||
async function isEnabled() {
|
||||
return OFFERS_SIGNUP.includes(await getMode())
|
||||
}
|
||||
|
||||
/** @throws if `mode` is not one of MODES — the caller validates first. */
|
||||
async function setMode(mode, updatedBy) {
|
||||
if (!MODES.includes(mode)) throw new Error(`unknown game-signup mode "${mode}"`)
|
||||
return settings.set(KEY, mode, updatedBy)
|
||||
}
|
||||
|
||||
module.exports = { KEY, MODES, OFFERS_SIGNUP, getMode, isEnabled, setMode }
|
||||
Reference in New Issue
Block a user