// ── The public demo's seed: this module's rows (MODULE_API 1.18.0, DM15) ──── // // `npm run seed:demo` hands each module its `modules.` section of the // fixture, with every `"$env:NAME"` already replaced, and the demo accounts' ids // (DEMO_MODE.md §7.3). This module writes two kinds of row from it, through its // own models; core never writes them: // // servers the link to the rig: the sidecar URL and token come from the host's // .env, so the token is never in the repo // links demo-player's stand-in Steam id (DM6), so the player screens have // somebody to show // // { "servers": [{ "id": "main", "name": "Demo", "sidecarBaseUrl": "$env:RUST_SIDECAR_URL", // "sidecarToken": "$env:RUST_SIDECAR_TOKEN" }], // "links": [{ "username": "demo-player", "steamId": "7656119…", "serverId": "main" }] } // // **It runs on every seed, so it upserts.** Core seeds content on a database's // first run only, but calls this every time. Everything is checked before // anything is written, and a problem throws, which fails the seed. const linksDb = require('./model/links/links.db') const servers = require('./model/servers/servers.model') const serversDb = require('./model/servers/servers.db') const sidecar = require('./sidecarClient') const core = require('./core') const log = core.logger('demo') const SERVER_ID = /^[a-z0-9][a-z0-9-]{0,63}$/ const STEAM_ID = /^7656119\d{10}$/ /** Every problem with the section, as sentences. Empty when it is good to write. */ function checkSection(data, accounts = {}) { const problems = [] if (!data || typeof data !== 'object' || Array.isArray(data)) return ['modules.rust must be an object'] const list = (key) => (data[key] === undefined ? [] : data[key]) for (const key of ['servers', 'links']) { if (data[key] !== undefined && !Array.isArray(data[key])) problems.push(`modules.rust.${key} must be an array`) } if (problems.length) return problems list('servers').forEach((s, i) => { const at = `modules.rust.servers[${i}]` if (!s || typeof s !== 'object') return problems.push(`${at} must be an object`) if (!SERVER_ID.test(String(s.id || ''))) problems.push(`${at}.id must be lowercase letters, digits and hyphens`) if (typeof s.name !== 'string' || !s.name.trim()) problems.push(`${at}.name is required`) if (!/^https?:\/\//.test(String(s.sidecarBaseUrl || ''))) problems.push(`${at}.sidecarBaseUrl must be an http(s) URL`) if (typeof s.sidecarToken !== 'string' || !s.sidecarToken) problems.push(`${at}.sidecarToken is required`) return undefined }) list('links').forEach((l, i) => { const at = `modules.rust.links[${i}]` if (!l || typeof l !== 'object') return problems.push(`${at} must be an object`) if (!accounts[l.username]) problems.push(`${at}.username must be one of the demo accounts`) if (!STEAM_ID.test(String(l.steamId || ''))) problems.push(`${at}.steamId must be a 17-digit Steam id`) if (l.serverId !== undefined && !SERVER_ID.test(String(l.serverId))) problems.push(`${at}.serverId must be a server id`) return undefined }) return problems } /** The hook core calls: `onDemoSeed(data, { accounts })`. */ async function onDemoSeed(data, { accounts = {} } = {}) { const problems = checkSection(data, accounts) if (problems.length) throw new Error(`the Rust demo seed has problems: ${problems.join('; ')}`) for (const s of data.servers || []) { await serversDb.upsertServer({ id: s.id, name: s.name.trim(), sidecarBaseUrl: s.sidecarBaseUrl, sidecarTokenEnc: servers.encryptToken(s.sidecarToken), protocol: s.protocol === undefined ? sidecar.PROTOCOL_VERSION : Number(s.protocol), enabled: s.enabled === undefined ? true : Boolean(s.enabled), sortOrder: s.sortOrder === undefined ? 0 : Number(s.sortOrder), }) log.info('demo server ready', { server: s.id }) } // A link is a plain insert by design (D23), so the upsert is spelled out here: // the right owner already holds it, or it is moved to them. Only the seed may // move a link, and only to a demo account. for (const l of data.links || []) { const userId = accounts[l.username] const steamId = String(l.steamId) const existing = await linksDb.getBySteamId(steamId) if (existing && Number(existing.userId) === Number(userId)) continue if (existing) await linksDb.removeBySteamId(steamId) await linksDb.insert({ steamId, userId, name: l.name || null, serverId: l.serverId || null }) log.info('demo link ready', { username: l.username }) } } module.exports = { checkSection, onDemoSeed }