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
143 lines
5.1 KiB
JavaScript
143 lines
5.1 KiB
JavaScript
// ── 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 }
|