`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>
64 lines
2.8 KiB
JavaScript
64 lines
2.8 KiB
JavaScript
// ── 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 }
|