Files
dwroller/database/scripts/apply_templates.js
T
alex dde3abda55 feat: Add validation and normalization utilities for player records
- Implemented `validatePlayer` and `normalizeTabInfo` functions in `database/validate.js` for player data validation and normalization.
- Added error handling for required fields and enforced data types and constraints.
- Introduced standardization for characteristics and skills.

feat: Create script for extracting rules from PDF files

- Developed `extract-rules.js` to extract text from PDF rulebooks and categorize rules.
- Integrated `pdftotext` for PDF processing and created a searchable rules database.
- Implemented rule categorization and indexing for efficient searching.

feat: Implement RulesTab component for rule searching

- Created `RulesTab.jsx` for searching and displaying rules with filtering options.
- Added recent searches and quick reference buttons for user convenience.
- Integrated API calls for fetching rules and displaying results dynamically.

feat: Add test script for Rules API

- Created `test-rules.js` to test the functionality of the Rules API endpoints.
- Included tests for search and stats endpoints to ensure proper response handling.
2025-08-16 12:15:32 +02:00

154 lines
5.4 KiB
JavaScript

#!/usr/bin/env node
/*
Apply templates and items to players.
Usage:
node apply_templates.js --mapping path/to/mapping.json [--apply]
If no mapping provided, creates a sample mapping at database/backups/template-mapping.sample.json and exits.
Mapping format (JSON):
{
"players": [
{
"name": "christoffer",
"setCharacteristics": { "WS": 35, "BS": 40 },
"addItems": ["Bolt Pistol"],
"setRenown": "Respected"
}
]
}
This script will only modify player tabInfo.inventory (array) and tabInfo.characteristics and renown. It backs up the DB before applying when --apply is used.
*/
const fs = require('fs');
const path = require('path');
const argv = require('yargs/yargs')(process.argv.slice(2)).argv;
const { playerHelpers } = require('../sqlite-db');
function backupDb(prefix) {
const ts = new Date().toISOString().replace(/[:.]/g,'');
const src = path.join(__dirname, '..', 'sqlite', 'deathwatch.db');
const dst = path.join(__dirname, '..', 'sqlite', `deathwatch.db.${prefix}.${ts}.bak`);
if (fs.existsSync(src)) {
fs.copyFileSync(src, dst);
return dst;
}
return null;
}
const armouryPath = path.join(__dirname, '..', '..', 'public', 'deathwatch-armoury.json');
let armoury = null;
try { armoury = JSON.parse(fs.readFileSync(armouryPath, 'utf8')); } catch (e) { armoury = null; }
const rulesPath = path.join(__dirname, '..', 'rules', 'rules-database.json');
let rulesDb = null;
try { rulesDb = JSON.parse(fs.readFileSync(rulesPath, 'utf8')); } catch (e) { rulesDb = null; }
const mappingPath = argv.mapping;
const apply = !!argv.apply;
if (!mappingPath) {
// create sample mapping file
const sample = {
players: playerHelpers.getAll().map(p=>({ name: p.name, setCharacteristics: {}, addItems: [], setRenown: null }))
};
const outDir = path.join(__dirname, '..', 'backups'); if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
const samplePath = path.join(outDir, `template-mapping.sample.${new Date().toISOString().replace(/[:.]/g,'')}.json`);
fs.writeFileSync(samplePath, JSON.stringify(sample, null, 2), 'utf8');
console.log('Sample mapping created at', samplePath);
console.log('Edit it to add templates/items and re-run with --mapping <path> --apply');
process.exit(0);
}
if (!fs.existsSync(mappingPath)) {
console.error('Mapping file not found:', mappingPath);
process.exit(2);
}
const mapping = JSON.parse(fs.readFileSync(mappingPath, 'utf8'));
const players = playerHelpers.getAll();
function findItemByName(name) {
if (!armoury) return null;
// armoury may be an object with items lists; search recursively
const candidates = [];
function search(obj) {
if (!obj) return;
if (Array.isArray(obj)) { obj.forEach(search); return; }
if (typeof obj === 'object') {
Object.keys(obj).forEach(k => search(obj[k]));
if (obj.name && typeof obj.name === 'string' && obj.name.toLowerCase().includes(name.toLowerCase())) {
candidates.push(obj);
}
}
}
search(armoury);
return candidates;
}
const report = [];
for (const m of (mapping.players || [])) {
const pName = m.name;
const pl = players.find(x=>x.name===pName);
if (!pl) { report.push({ name: pName, error: 'player not found' }); continue; }
const before = JSON.parse(JSON.stringify(pl.tabInfo || {}));
const after = JSON.parse(JSON.stringify(before));
let changed = false;
// set characteristics
if (m.setCharacteristics && typeof m.setCharacteristics === 'object') {
after.characteristics = after.characteristics || {};
Object.keys(m.setCharacteristics).forEach(k=>{ after.characteristics[k] = m.setCharacteristics[k]; });
changed = true;
}
// set renown
if (m.setRenown) { after.renown = m.setRenown; changed = true; }
// add items
if (Array.isArray(m.addItems) && m.addItems.length>0) {
after.inventory = Array.isArray(after.inventory) ? after.inventory.slice() : [];
for (const itemName of m.addItems) {
const found = findItemByName(itemName);
if (found && found.length>0) {
// push item summary
after.inventory.push({ name: found[0].name || itemName, source: found[0].source || 'armoury' });
} else {
after.inventory.push({ name: itemName, source: 'manual' });
}
}
changed = true;
}
report.push({ name: pName, changed, before, after });
}
const summary = { applied: apply, results: report };
if (apply) {
const backup = backupDb('pre_apply_templates');
let appliedCount=0;
for (const r of report) {
if (r.changed) {
try {
const pl = playerHelpers.getByName(r.name);
const merged = Object.assign({}, pl, { tabInfo: r.after });
playerHelpers.update(r.name, merged);
appliedCount++;
} catch (e) { console.error('Failed to apply for', r.name, e); }
}
}
summary.appliedCount = appliedCount;
summary.backup = backup;
const outPath = path.join(__dirname, '..', 'backups', `apply_templates.result.${new Date().toISOString().replace(/[:.]/g,'')}.json`);
fs.writeFileSync(outPath, JSON.stringify(summary, null, 2),'utf8');
console.log('Applied to', appliedCount, 'players. Backup at', backup, 'Result saved to', outPath);
} else {
const outPath = path.join(__dirname, '..', 'backups', `apply_templates.dryrun.${new Date().toISOString().replace(/[:.]/g,'')}.json`);
fs.writeFileSync(outPath, JSON.stringify(summary, null, 2),'utf8');
console.log('Dry-run result written to', outPath);
console.log(JSON.stringify(summary, null, 2));
}