Replace baked-in UOM/MysticMoon/UOMysticmoon branding with a BRAND_* env scheme so one prebuilt image runs as any shard; UOMysticmoon becomes the first tenant that sets these vars rather than a special case in the code. Architecture (chosen because the app ships as a prebuilt image): - server/src/config/brand.js + bot/src/brand.js read BRAND_* once at boot, with Runic Gateway defaults. - Text/colors reach the SPA at RUNTIME through the existing public settings API (settings.model.getPublic -> SiteContext), so no client rebuild. The admin-editable site title + contact email still override BRAND_NAME/email. - SiteContext applies BRAND_ACCENT_COLOR to the --accent CSS var at runtime. - Express templates the built index.html <title>/description/OG/favicon at serve time from BRAND_* (renderIndexHtml in app.js). - Server-side consumers read brand directly: emails, TOTP issuer, API docs, boot logs, HTML error page. Bot uses it for embed color + logs. Assets: logo/hero/favicon delivered from a ./brand:/app/brand bind-mount (BRAND_LOGO/HERO/FAVICON), with neutral defaults baked in; hero falls back to a built-in image when unset. Scope: also genericized package.json names (uomysticmoon-* -> runic-gateway-*) and the DB_NAME/DB_USER/COOKIE_NAME code defaults (runic_gateway/runic/ rg_token). Production keeps its real values by pinning them in .env — see .env.uomysticmoon.example, which reproduces the exact UOMysticmoon identity (proof the substitution works). Changing a deployed COOKIE_NAME invalidates existing sessions, so UOMysticmoon pins uomm_token. Verified: 193 server tests pass, client builds, app.js loads + templates the built index.html, brand transform injects title/description/OG/favicon.
127 lines
4.3 KiB
JavaScript
127 lines
4.3 KiB
JavaScript
const settingsDb = require('./settings.db')
|
|
const brand = require('../../config/brand')
|
|
|
|
// Keys safe to expose on the public site.
|
|
const PUBLIC_KEYS = [
|
|
'site_mode',
|
|
'maintenance_message',
|
|
'status_message',
|
|
'homepage_teaser',
|
|
'contact_email',
|
|
'site_title',
|
|
'hero_layout', // portal hero composition (JSON). Draft key stays admin-only.
|
|
]
|
|
|
|
// Player self-registration mode. Stored under the 'player_registration' key.
|
|
// NOTE: the raw value is never exposed publicly — getPublic() derives boolean
|
|
// availability flags from it instead (see below).
|
|
const REGISTRATION_KEY = 'player_registration'
|
|
const REGISTRATION_MODES = ['disabled', 'password', 'sso', 'both']
|
|
|
|
// Resolve the registration mode, defaulting to 'disabled' (and coercing any
|
|
// unexpected stored value back to 'disabled' so a bad row can't open sign-up).
|
|
async function getRegistrationMode() {
|
|
const value = await settingsDb.get(REGISTRATION_KEY)
|
|
return REGISTRATION_MODES.includes(value) ? value : 'disabled'
|
|
}
|
|
|
|
// Derived, public-safe availability flags for the register page.
|
|
function registrationFlags(mode) {
|
|
return {
|
|
password: mode === 'password' || mode === 'both',
|
|
sso: mode === 'sso' || mode === 'both',
|
|
}
|
|
}
|
|
|
|
// Game-account signup (Protocol 2.0). The admin picks who mints game accounts:
|
|
// disabled — the site never offers game-account creation (link-only).
|
|
// website — the site is the authority (offer creation; pair with the shard in
|
|
// website mode + AutoCreateAccounts=false).
|
|
// hybrid — either side may create (the site offers creation).
|
|
// game — the game server is the authority; the site does NOT offer creation.
|
|
// The site OFFERS creation only for 'website'/'hybrid'; the shard's own SignupMode
|
|
// (Bridge.cfg) still has the final say and may 403 a call regardless.
|
|
const GAME_SIGNUP_KEY = 'game_account_signup'
|
|
const GAME_SIGNUP_MODES = ['disabled', 'website', 'hybrid', 'game']
|
|
const GAME_SIGNUP_OFFER = ['website', 'hybrid']
|
|
|
|
async function getGameSignupMode() {
|
|
const v = await settingsDb.get(GAME_SIGNUP_KEY)
|
|
return GAME_SIGNUP_MODES.includes(v) ? v : 'disabled'
|
|
}
|
|
|
|
async function isGameAccountSignupEnabled() {
|
|
return GAME_SIGNUP_OFFER.includes(await getGameSignupMode())
|
|
}
|
|
|
|
async function get(key) {
|
|
return settingsDb.get(key)
|
|
}
|
|
|
|
async function set(key, value, updatedBy = null) {
|
|
return settingsDb.set(key, value, updatedBy)
|
|
}
|
|
|
|
async function setMany(obj, updatedBy = null) {
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
await settingsDb.set(key, value, updatedBy)
|
|
}
|
|
}
|
|
|
|
async function getAll() {
|
|
const rows = await settingsDb.getAll()
|
|
return rows.reduce((acc, row) => {
|
|
acc[row.key] = row.value
|
|
return acc
|
|
}, {})
|
|
}
|
|
|
|
async function getPublic() {
|
|
const all = await getAll()
|
|
const out = PUBLIC_KEYS.reduce((acc, key) => {
|
|
if (all[key] !== undefined) acc[key] = all[key]
|
|
return acc
|
|
}, {})
|
|
// Derived registration availability (never the raw mode). Lets the register
|
|
// page show/hide the password form and SSO buttons.
|
|
const mode = REGISTRATION_MODES.includes(all[REGISTRATION_KEY]) ? all[REGISTRATION_KEY] : 'disabled'
|
|
out.registration = registrationFlags(mode)
|
|
// Whether the site offers game-account creation (the shard's own mode still has
|
|
// the final say when the call is made). Lets the portal show/hide the form.
|
|
const gsMode = GAME_SIGNUP_MODES.includes(all[GAME_SIGNUP_KEY]) ? all[GAME_SIGNUP_KEY] : 'disabled'
|
|
out.gameAccountSignup = GAME_SIGNUP_OFFER.includes(gsMode)
|
|
// Instance branding (BRAND_* env defaults). The two admin-editable settings —
|
|
// site title and contact email — override the env value when set, so existing
|
|
// installs keep their DB-configured name; everything else comes from env.
|
|
out.brand = {
|
|
name: out.site_title || brand.name,
|
|
shortName: brand.shortName,
|
|
tagline: brand.tagline,
|
|
description: brand.description,
|
|
contactEmail: out.contact_email || brand.contactEmail,
|
|
url: brand.url,
|
|
accent: brand.accent,
|
|
logo: brand.logo,
|
|
hero: brand.hero,
|
|
favicon: brand.favicon,
|
|
}
|
|
return out
|
|
}
|
|
|
|
module.exports = {
|
|
get,
|
|
set,
|
|
setMany,
|
|
getAll,
|
|
getPublic,
|
|
PUBLIC_KEYS,
|
|
REGISTRATION_KEY,
|
|
REGISTRATION_MODES,
|
|
getRegistrationMode,
|
|
registrationFlags,
|
|
GAME_SIGNUP_KEY,
|
|
GAME_SIGNUP_MODES,
|
|
getGameSignupMode,
|
|
isGameAccountSignupEnabled,
|
|
}
|