feat(rust): zone presets, and the zone step's options, messages and dome (PLAN_REDESIGNS §3, D210)
Admin → Rust zone presets: named sets of ZoneManager flags and settings for one server, several, or every server (D195), ticked in groups read from each server's own ZoneManager list in its last hello (D211). A flag not on a covered server is refused on save with the server named; two presets of one name may not share a server. rust.zone.open gains options (one line, NoBuild, radiation=10), enterMessage, leaveMessage, delivery, dome and domeStack. The options field's dropdown is rust.options.zone_presets, whose row VALUE is the preset's line, so picking one copies it into the step (D210) and no published event changes when a preset does. The line and the dome are checked against the server's hello on save and in a dry run; bad-option and dome-unavailable are permanent. Schema: rust_zone_presets, rust_zone_preset_servers. Swagger fragment and routes.manifest regenerated against the pinned core f0e7d2a. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
142
server/model/zones/zones.db.js
Normal file
142
server/model/zones/zones.db.js
Normal file
@@ -0,0 +1,142 @@
|
||||
// ── SQL for zone presets, and what each server said about its zone mods ──────
|
||||
//
|
||||
// The presets are the module's own (PLAN_REDESIGNS §3.1, D195). What a server
|
||||
// has — its ZoneManager's flag list (D211), ZoneDomes and the domes helper (D194)
|
||||
// — is read out of the hello each server's state row keeps whole (`raw`), so the
|
||||
// preset form works while a server is off.
|
||||
|
||||
const core = require('../../core')
|
||||
|
||||
const PRESETS = 'rust_zone_presets'
|
||||
const PRESET_SERVERS = 'rust_zone_preset_servers'
|
||||
const SERVERS = 'rust_servers'
|
||||
const STATE = 'rust_server_state'
|
||||
|
||||
/** The driver hands JSON_EXTRACT and TEXT back as strings. A bad value is no value. */
|
||||
function json(value, fallback) {
|
||||
if (value === null || value === undefined) return fallback
|
||||
if (typeof value !== 'string') return value
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch {
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
function shape(row, serverIds) {
|
||||
return {
|
||||
id: Number(row.id),
|
||||
name: row.name,
|
||||
flags: json(row.flags, []),
|
||||
settings: json(row.settings, {}),
|
||||
allServers: Boolean(row.allServers),
|
||||
servers: serverIds,
|
||||
updatedAt: row.updatedAt ? new Date(row.updatedAt).toISOString() : null,
|
||||
}
|
||||
}
|
||||
|
||||
/** Every preset, with the servers each names, oldest first. */
|
||||
async function listPresets() {
|
||||
const [rows, links] = await Promise.all([
|
||||
core.query(
|
||||
`SELECT id, name, flags, settings, all_servers AS allServers, updated_at AS updatedAt
|
||||
FROM ${PRESETS} ORDER BY name ASC, id ASC`,
|
||||
),
|
||||
core.query(`SELECT preset_id AS presetId, server_id AS serverId FROM ${PRESET_SERVERS} ORDER BY server_id ASC`),
|
||||
])
|
||||
const by = new Map()
|
||||
for (const l of links) {
|
||||
const id = Number(l.presetId)
|
||||
if (!by.has(id)) by.set(id, [])
|
||||
by.get(id).push(l.serverId)
|
||||
}
|
||||
return rows.map((r) => shape(r, by.get(Number(r.id)) || []))
|
||||
}
|
||||
|
||||
async function getPreset(id) {
|
||||
const rows = await core.query(
|
||||
`SELECT id, name, flags, settings, all_servers AS allServers, updated_at AS updatedAt
|
||||
FROM ${PRESETS} WHERE id = ?`,
|
||||
[id],
|
||||
)
|
||||
if (!rows[0]) return null
|
||||
const links = await core.query(`SELECT server_id AS serverId FROM ${PRESET_SERVERS} WHERE preset_id = ? ORDER BY server_id ASC`, [id])
|
||||
return shape(rows[0], links.map((l) => l.serverId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes one preset and replaces its server list. The model has checked every
|
||||
* server exists first, so the inserts below have nothing left to refuse. The
|
||||
* list is written in one statement, so a preset is never left half-listed.
|
||||
*/
|
||||
async function savePreset({ id = null, name, flags, settings, allServers, servers }, userId = null) {
|
||||
let presetId = id
|
||||
if (presetId) {
|
||||
await core.query(
|
||||
`UPDATE ${PRESETS} SET name = ?, flags = ?, settings = ?, all_servers = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
|
||||
[name, JSON.stringify(flags), JSON.stringify(settings), allServers ? 1 : 0, presetId],
|
||||
)
|
||||
await core.query(`DELETE FROM ${PRESET_SERVERS} WHERE preset_id = ?`, [presetId])
|
||||
} else {
|
||||
const res = await core.query(
|
||||
`INSERT INTO ${PRESETS} (name, flags, settings, all_servers, created_by) VALUES (?, ?, ?, ?, ?)`,
|
||||
[name, JSON.stringify(flags), JSON.stringify(settings), allServers ? 1 : 0, userId],
|
||||
)
|
||||
presetId = Number(res.insertId)
|
||||
}
|
||||
if (!allServers && servers.length > 0) {
|
||||
await core.query(
|
||||
`INSERT INTO ${PRESET_SERVERS} (preset_id, server_id) VALUES ${servers.map(() => '(?, ?)').join(', ')}`,
|
||||
servers.flatMap((serverId) => [presetId, serverId]),
|
||||
)
|
||||
}
|
||||
return presetId
|
||||
}
|
||||
|
||||
async function deletePreset(id) {
|
||||
await core.query(`DELETE FROM ${PRESETS} WHERE id = ?`, [id])
|
||||
}
|
||||
|
||||
/**
|
||||
* Each configured server, in the operator's order, with what its last hello said
|
||||
* about ZoneManager (`{ version, flags }`) and ZoneDomes (`{ loaded, version,
|
||||
* helper, ready }`). Either is null for a server that never said: no ZoneManager,
|
||||
* a plugin before protocol 13, or a server never reached.
|
||||
*/
|
||||
async function listZoneServers() {
|
||||
const rows = await core.query(
|
||||
`SELECT s.id, s.name, s.enabled,
|
||||
JSON_EXTRACT(st.raw, '$.zoneManager') AS zoneManager,
|
||||
JSON_EXTRACT(st.raw, '$.integrations.zoneDomes') AS zoneDomes
|
||||
FROM ${SERVERS} s
|
||||
LEFT JOIN ${STATE} st ON st.server_id = s.id
|
||||
ORDER BY s.sort_order ASC, s.id ASC`,
|
||||
)
|
||||
return rows.map((r) => {
|
||||
const zm = json(r.zoneManager, null)
|
||||
const zd = json(r.zoneDomes, null)
|
||||
return {
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
enabled: Boolean(r.enabled),
|
||||
zoneManager:
|
||||
zm && typeof zm === 'object'
|
||||
? {
|
||||
version: typeof zm.version === 'string' ? zm.version : null,
|
||||
flags: Array.isArray(zm.flags) ? zm.flags.filter((f) => typeof f === 'string') : null,
|
||||
}
|
||||
: null,
|
||||
zoneDomes:
|
||||
zd && typeof zd === 'object'
|
||||
? {
|
||||
loaded: zd.loaded === true,
|
||||
version: typeof zd.version === 'string' ? zd.version : null,
|
||||
helper: zd.helper && typeof zd.helper.state === 'string' ? zd.helper : null,
|
||||
ready: zd.ready === true,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { PRESETS, PRESET_SERVERS, listPresets, getPreset, savePreset, deletePreset, listZoneServers }
|
||||
Reference in New Issue
Block a user