Files
craftdock/src/routes/properties.js
T
Lucas RayanandClaude Sonnet 5.5 640e52c0c9 Initial commit: CraftDock as of 2026-11-01
The previous GitHub history was lost with the account (the local .git was missing
its refs and most objects), so this repository starts from the existing code.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 22:44:39 -03:00

76 lines
3.0 KiB
JavaScript

import { Router } from 'express';
// Managed keys: type drives validation; `default` (vanilla server default) is
// filled into GET responses when the key is missing from server.properties, so
// the Options tab always starts fully populated. motd intentionally has none.
export const PROPERTY_SCHEMA = {
difficulty: { type: 'enum', values: ['peaceful', 'easy', 'normal', 'hard'], default: 'normal' },
gamemode: { type: 'enum', values: ['survival', 'creative', 'adventure', 'spectator'], default: 'survival' },
pvp: { type: 'boolean', default: 'true' },
'allow-flight': { type: 'boolean', default: 'false' },
hardcore: { type: 'boolean', default: 'false' },
'keep-inventory': { type: 'boolean', default: 'false' },
'allow-nether': { type: 'boolean', default: 'true' },
'enable-command-block': { type: 'boolean', default: 'false' },
'force-gamemode': { type: 'boolean', default: 'false' },
'spawn-monsters': { type: 'boolean', default: 'true' },
'spawn-animals': { type: 'boolean', default: 'true' },
'spawn-npcs': { type: 'boolean', default: 'true' },
'max-players': { type: 'int', default: '20' },
'view-distance': { type: 'int', default: '10' },
'simulation-distance': { type: 'int', default: '10' },
'spawn-protection': { type: 'int', default: '16' },
motd: { type: 'string' },
'level-name': { type: 'string' },
'level-seed': { type: 'string' },
};
// Merge schema defaults under the actual file contents (file always wins).
export function withDefaults(properties) {
const out = { ...properties };
for (const [key, schema] of Object.entries(PROPERTY_SCHEMA)) {
if (schema.default !== undefined && out[key] === undefined) out[key] = schema.default;
}
return out;
}
function validate(properties) {
for (const [key, value] of Object.entries(properties)) {
const schema = PROPERTY_SCHEMA[key];
if (!schema) continue; // unmanaged keys pass through
const str = String(value);
if (schema.type === 'enum' && !schema.values.includes(str)) return key;
if (schema.type === 'boolean' && str !== 'true' && str !== 'false') return key;
if (schema.type === 'int' && !/^-?\d+$/.test(str)) return key;
}
return null;
}
export function createPropertiesRouter({ propertiesService }) {
const router = Router();
router.get('/', async (req, res, next) => {
try {
let current = {};
try {
current = await propertiesService.read();
} catch (err) {
if (err?.code !== 'ENOENT') throw err; // no file yet → serve defaults
}
res.json({ properties: withDefaults(current) });
} catch (err) { next(err); }
});
router.put('/', async (req, res, next) => {
const properties = req.body?.properties || {};
const bad = validate(properties);
if (bad) return res.status(400).json({ error: 'invalid_value', field: bad });
try {
const merged = await propertiesService.update(properties);
res.json({ ok: true, properties: merged });
} catch (err) { next(err); }
});
return router;
}