|
|
|
|
@@ -9,18 +9,44 @@
|
|
|
|
|
// .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
|
|
|
|
|
// npcs the NPC faction table and profiles of the demo default set (DM26)
|
|
|
|
|
// chat a BetterChat style per group (DM26)
|
|
|
|
|
//
|
|
|
|
|
// { "servers": [{ "id": "main", "name": "Demo", "sidecarBaseUrl": "$env:RUST_SIDECAR_URL",
|
|
|
|
|
// "sidecarToken": "$env:RUST_SIDECAR_TOKEN" }],
|
|
|
|
|
// "links": [{ "username": "demo-player", "steamId": "7656119…", "serverId": "main" }] }
|
|
|
|
|
// "links": [{ "username": "demo-player", "steamId": "7656119…", "serverId": "main" }],
|
|
|
|
|
// "npcs": { "factions": [{ "a": "bandits", "b": "guards", "relation": "allied" }],
|
|
|
|
|
// "profiles": [{ "name": "bandit", "servers": ["main"], "body": { … } }] },
|
|
|
|
|
// "chat": [{ "serverId": "main", "group": "vip", "style": { "Title": "[VIP]", … } }] }
|
|
|
|
|
//
|
|
|
|
|
// **Why NPCs and chat come from here, not the rig's baseline (DM26).** The rig is
|
|
|
|
|
// restored before the site is wiped, and a fresh site imports what it finds
|
|
|
|
|
// (§7.1). That holds for permissions, but not for these two. The restored rig's
|
|
|
|
|
// RunicNPC says it is already managed, by the site before this one, so the new
|
|
|
|
|
// site adopts nothing (D244) and its first push empties the profiles. The import
|
|
|
|
|
// never adopts a group's chat style. So the site holds them from the start and
|
|
|
|
|
// pushes them, and the placements in the baseline spawn again.
|
|
|
|
|
//
|
|
|
|
|
// A chat style names a group the rig's import has not brought in yet, because
|
|
|
|
|
// the seed runs first. The group is created for that server, and the import
|
|
|
|
|
// then adopts the rig's title, rank and permissions into it. A style lists only
|
|
|
|
|
// the fields that differ from BetterChat's defaults.
|
|
|
|
|
//
|
|
|
|
|
// **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.
|
|
|
|
|
// first run only, but calls this every time. The section's shape is checked
|
|
|
|
|
// before anything is written, and a problem throws, which fails the seed. A
|
|
|
|
|
// profile is checked by the NPC model as it is saved, as the admin page saves
|
|
|
|
|
// one, so a profile the model refuses fails the seed too.
|
|
|
|
|
|
|
|
|
|
const linksDb = require('./model/links/links.db')
|
|
|
|
|
const servers = require('./model/servers/servers.model')
|
|
|
|
|
const serversDb = require('./model/servers/servers.db')
|
|
|
|
|
const npcs = require('./model/npcs/npcs.model')
|
|
|
|
|
const npcsDb = require('./model/npcs/npcs.db')
|
|
|
|
|
const npcShape = require('./model/npcs/npcProfile')
|
|
|
|
|
const permApply = require('./model/permissions/permissions.apply')
|
|
|
|
|
const permDb = require('./model/permissions/permissions.db')
|
|
|
|
|
const chatStyle = require('./model/permissions/chatStyle')
|
|
|
|
|
const sidecar = require('./sidecarClient')
|
|
|
|
|
const core = require('./core')
|
|
|
|
|
|
|
|
|
|
@@ -28,6 +54,13 @@ const log = core.logger('demo')
|
|
|
|
|
|
|
|
|
|
const SERVER_ID = /^[a-z0-9][a-z0-9-]{0,63}$/
|
|
|
|
|
const STEAM_ID = /^7656119\d{10}$/
|
|
|
|
|
// Oxide group names as the permission model keeps them: trimmed and lower-cased.
|
|
|
|
|
const GROUP_NAME = /^[a-z0-9_.-]{1,64}$/
|
|
|
|
|
|
|
|
|
|
/** BetterChat's defaults for the group, with the fixture's fields over them. */
|
|
|
|
|
function fullStyle(group, style) {
|
|
|
|
|
return { ...chatStyle.defaults(String(group || '')), ...(style && typeof style === 'object' ? style : {}) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Every problem with the section, as sentences. Empty when it is good to write. */
|
|
|
|
|
function checkSection(data, accounts = {}) {
|
|
|
|
|
@@ -35,11 +68,41 @@ function checkSection(data, accounts = {}) {
|
|
|
|
|
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']) {
|
|
|
|
|
for (const key of ['servers', 'links', 'chat']) {
|
|
|
|
|
if (data[key] !== undefined && !Array.isArray(data[key])) problems.push(`modules.rust.${key} must be an array`)
|
|
|
|
|
}
|
|
|
|
|
const n = data.npcs
|
|
|
|
|
if (n !== undefined) {
|
|
|
|
|
if (!n || typeof n !== 'object' || Array.isArray(n)) problems.push('modules.rust.npcs must be an object')
|
|
|
|
|
else {
|
|
|
|
|
if (n.factions !== undefined && !Array.isArray(n.factions)) problems.push('modules.rust.npcs.factions must be an array')
|
|
|
|
|
if (n.profiles !== undefined && !Array.isArray(n.profiles)) problems.push('modules.rust.npcs.profiles must be an array')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (problems.length) return problems
|
|
|
|
|
|
|
|
|
|
if (n && n.factions !== undefined) {
|
|
|
|
|
const checked = npcShape.checkFactions(n.factions)
|
|
|
|
|
if (!checked.ok) problems.push(`modules.rust.npcs.${checked.error}`)
|
|
|
|
|
}
|
|
|
|
|
;((n && n.profiles) || []).forEach((p, i) => {
|
|
|
|
|
const at = `modules.rust.npcs.profiles[${i}]`
|
|
|
|
|
if (!p || typeof p !== 'object') return problems.push(`${at} must be an object`)
|
|
|
|
|
if (!npcShape.NAME_RULE.test(String(p.name || ''))) problems.push(`${at}.name must be 1 to 40 of a-z, 0-9, _ and -`)
|
|
|
|
|
if (!p.body || typeof p.body !== 'object' || Array.isArray(p.body)) problems.push(`${at}.body must be an object`)
|
|
|
|
|
return undefined
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
list('chat').forEach((c, i) => {
|
|
|
|
|
const at = `modules.rust.chat[${i}]`
|
|
|
|
|
if (!c || typeof c !== 'object') return problems.push(`${at} must be an object`)
|
|
|
|
|
if (!SERVER_ID.test(String(c.serverId || ''))) problems.push(`${at}.serverId must be a server id`)
|
|
|
|
|
if (!GROUP_NAME.test(String(c.group || ''))) problems.push(`${at}.group must be a group name`)
|
|
|
|
|
const checked = chatStyle.validateStyle(fullStyle(c.group, c.style))
|
|
|
|
|
if (!checked.ok) problems.push(`${at}.style: ${checked.errors.join(' ')}`)
|
|
|
|
|
return undefined
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
list('servers').forEach((s, i) => {
|
|
|
|
|
const at = `modules.rust.servers[${i}]`
|
|
|
|
|
if (!s || typeof s !== 'object') return problems.push(`${at} must be an object`)
|
|
|
|
|
@@ -92,6 +155,36 @@ async function onDemoSeed(data, { accounts = {} } = {}) {
|
|
|
|
|
await linksDb.insert({ steamId, userId, name: l.name || null, serverId: l.serverId || null })
|
|
|
|
|
log.info('demo link ready', { username: l.username })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// After the servers: a profile is refused for a server the site does not have.
|
|
|
|
|
const n = data.npcs || {}
|
|
|
|
|
if (n.factions !== undefined) {
|
|
|
|
|
await npcs.setFactions(n.factions)
|
|
|
|
|
log.info('demo NPC factions ready', { pairs: n.factions.length })
|
|
|
|
|
}
|
|
|
|
|
if (n.profiles && n.profiles.length) {
|
|
|
|
|
const existing = await npcsDb.listProfiles()
|
|
|
|
|
for (const p of n.profiles) {
|
|
|
|
|
const input = { name: p.name, body: p.body, servers: p.servers, allServers: p.allServers, killsScope: p.killsScope }
|
|
|
|
|
const same = existing.find((e) => e.name === p.name && !e.replaced)
|
|
|
|
|
if (same) await npcs.update(same.id, input)
|
|
|
|
|
else await npcs.create(input)
|
|
|
|
|
log.info('demo NPC profile ready', { profile: p.name })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const c of data.chat || []) {
|
|
|
|
|
const name = String(c.group)
|
|
|
|
|
let group = await permApply.groupOn(name, c.serverId)
|
|
|
|
|
if (!group) {
|
|
|
|
|
const id = await permDb.insertGroup({ name, title: name })
|
|
|
|
|
await permDb.setGroupServers(id, { allServers: false, servers: [c.serverId] })
|
|
|
|
|
group = { id }
|
|
|
|
|
}
|
|
|
|
|
await permDb.setGroupChat(group.id, chatStyle.validateStyle(fullStyle(name, c.style)).fields)
|
|
|
|
|
await permDb.markDirty(c.serverId)
|
|
|
|
|
log.info('demo chat style ready', { server: c.serverId, group: name })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = { checkSection, onDemoSeed }
|
|
|
|
|
|