sandbox/server/routes/characters.js

239 lines
12 KiB
JavaScript

import { Router } from 'express';
import { v4 as uuidv4 } from 'uuid';
import db from '../db.js';
import { authenticateToken } from '../middleware/auth.js';
const router = Router();
router.get('/', authenticateToken, (req, res) => {
const characters = db.prepare('SELECT * FROM characters WHERE user_id = ? ORDER BY updated_at DESC').all(req.userId);
res.json({ characters });
});
router.post('/', authenticateToken, (req, res) => {
const { name, description, personality_traits, backstory } = req.body;
if (!name) return res.status(400).json({ error: 'Name is required' });
const id = uuidv4();
const traits = JSON.stringify(personality_traits || []);
db.prepare('INSERT INTO characters (id, user_id, name, description, personality_traits, backstory) VALUES (?, ?, ?, ?, ?, ?)')
.run(id, req.userId, name, description || '', traits, backstory || '');
const character = db.prepare('SELECT * FROM characters WHERE id = ?').get(id);
res.status(201).json({ character });
});
router.get('/:id', authenticateToken, (req, res) => {
const character = db.prepare('SELECT * FROM characters WHERE id = ? AND user_id = ?').get(req.params.id, req.userId);
if (!character) return res.status(404).json({ error: 'Character not found' });
const needs = db.prepare('SELECT * FROM character_needs WHERE character_id = ?').all(req.params.id);
const uiElements = db.prepare('SELECT * FROM character_ui_elements WHERE character_id = ?').all(req.params.id);
const brainRules = db.prepare('SELECT * FROM character_brain_rules WHERE character_id = ?').all(req.params.id);
res.json({ character, needs, ui_elements: uiElements, brain_rules: brainRules });
});
router.put('/:id', authenticateToken, (req, res) => {
const { name, description, personality_traits, backstory, avatar_url } = req.body;
const existing = db.prepare('SELECT * FROM characters WHERE id = ? AND user_id = ?').get(req.params.id, req.userId);
if (!existing) return res.status(404).json({ error: 'Character not found' });
db.prepare('UPDATE characters SET name = ?, description = ?, personality_traits = ?, backstory = ?, avatar_url = ?, updated_at = datetime(\'now\') WHERE id = ?')
.run(
name || existing.name,
description !== undefined ? description : existing.description,
personality_traits ? JSON.stringify(personality_traits) : existing.personality_traits,
backstory !== undefined ? backstory : existing.backstory,
avatar_url !== undefined ? avatar_url : existing.avatar_url,
req.params.id
);
const character = db.prepare('SELECT * FROM characters WHERE id = ?').get(req.params.id);
res.json({ character });
});
router.delete('/:id', authenticateToken, (req, res) => {
const existing = db.prepare('SELECT * FROM characters WHERE id = ? AND user_id = ?').get(req.params.id, req.userId);
if (!existing) return res.status(404).json({ error: 'Character not found' });
db.prepare('DELETE FROM characters WHERE id = ?').run(req.params.id);
res.json({ message: 'Character deleted' });
});
// --- Needs ---
router.get('/:id/needs', authenticateToken, (req, res) => {
const needs = db.prepare('SELECT * FROM character_needs WHERE character_id = ?').all(req.params.id);
res.json({ needs });
});
router.post('/:id/needs', authenticateToken, (req, res) => {
const { name, enabled, initial_value, min_value, max_value, decay_rate, priority } = req.body;
if (!name) return res.status(400).json({ error: 'Need name is required' });
const needId = uuidv4();
db.prepare('INSERT INTO character_needs (id, character_id, name, enabled, initial_value, min_value, max_value, decay_rate, priority) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)')
.run(needId, req.params.id, name, enabled !== false ? 1 : 0, initial_value || 50, min_value || 0, max_value || 100, decay_rate || 1, priority || 0);
const need = db.prepare('SELECT * FROM character_needs WHERE id = ?').get(needId);
res.status(201).json({ need });
});
router.put('/:id/needs/:needId', authenticateToken, (req, res) => {
const { name, enabled, initial_value, min_value, max_value, decay_rate, priority } = req.body;
const existing = db.prepare('SELECT * FROM character_needs WHERE id = ? AND character_id = ?').get(req.params.needId, req.params.id);
if (!existing) return res.status(404).json({ error: 'Need not found' });
db.prepare('UPDATE character_needs SET name = ?, enabled = ?, initial_value = ?, min_value = ?, max_value = ?, decay_rate = ?, priority = ? WHERE id = ?')
.run(name || existing.name, enabled !== undefined ? (enabled ? 1 : 0) : existing.enabled, initial_value ?? existing.initial_value, min_value ?? existing.min_value, max_value ?? existing.max_value, decay_rate ?? existing.decay_rate, priority ?? existing.priority, req.params.needId);
const need = db.prepare('SELECT * FROM character_needs WHERE id = ?').get(req.params.needId);
res.json({ need });
});
router.delete('/:id/needs/:needId', authenticateToken, (req, res) => {
const existing = db.prepare('SELECT * FROM character_needs WHERE id = ? AND character_id = ?').get(req.params.needId, req.params.id);
if (!existing) return res.status(404).json({ error: 'Need not found' });
db.prepare('DELETE FROM character_needs WHERE id = ?').run(req.params.needId);
res.json({ message: 'Need deleted' });
});
// --- UI Elements ---
router.get('/:id/ui-elements', authenticateToken, (req, res) => {
const elements = db.prepare('SELECT * FROM character_ui_elements WHERE character_id = ?').all(req.params.id);
res.json({ ui_elements: elements });
});
router.post('/:id/ui-elements', authenticateToken, (req, res) => {
const { need_id, element_type, config } = req.body;
if (!element_type) return res.status(400).json({ error: 'Element type is required' });
const elementId = uuidv4();
db.prepare('INSERT INTO character_ui_elements (id, character_id, need_id, element_type, config) VALUES (?, ?, ?, ?, ?)')
.run(elementId, req.params.id, need_id || null, element_type, JSON.stringify(config || {}));
const element = db.prepare('SELECT * FROM character_ui_elements WHERE id = ?').get(elementId);
res.status(201).json({ ui_element: element });
});
router.put('/:id/ui-elements/:elementId', authenticateToken, (req, res) => {
const { need_id, element_type, config } = req.body;
const existing = db.prepare('SELECT * FROM character_ui_elements WHERE id = ? AND character_id = ?').get(req.params.elementId, req.params.id);
if (!existing) return res.status(404).json({ error: 'UI element not found' });
db.prepare('UPDATE character_ui_elements SET need_id = ?, element_type = ?, config = ? WHERE id = ?')
.run(need_id !== undefined ? need_id : existing.need_id, element_type || existing.element_type, config ? JSON.stringify(config) : existing.config, req.params.elementId);
const element = db.prepare('SELECT * FROM character_ui_elements WHERE id = ?').get(req.params.elementId);
res.json({ ui_element: element });
});
router.delete('/:id/ui-elements/:elementId', authenticateToken, (req, res) => {
const existing = db.prepare('SELECT * FROM character_ui_elements WHERE id = ? AND character_id = ?').get(req.params.elementId, req.params.id);
if (!existing) return res.status(404).json({ error: 'UI element not found' });
db.prepare('DELETE FROM character_ui_elements WHERE id = ?').run(req.params.elementId);
res.json({ message: 'UI element deleted' });
});
// --- Brain Rules ---
router.get('/:id/brain-rules', authenticateToken, (req, res) => {
const rules = db.prepare('SELECT * FROM character_brain_rules WHERE character_id = ?').all(req.params.id);
res.json({ brain_rules: rules });
});
router.post('/:id/brain-rules', authenticateToken, (req, res) => {
const { condition, action, priority, enabled } = req.body;
if (!condition || !action) return res.status(400).json({ error: 'Condition and action are required' });
const ruleId = uuidv4();
db.prepare('INSERT INTO character_brain_rules (id, character_id, condition, action, priority, enabled) VALUES (?, ?, ?, ?, ?, ?)')
.run(ruleId, req.params.id, JSON.stringify(condition), JSON.stringify(action), priority || 0, enabled !== false ? 1 : 0);
const rule = db.prepare('SELECT * FROM character_brain_rules WHERE id = ?').get(ruleId);
res.status(201).json({ brain_rule: rule });
});
router.put('/:id/brain-rules/:ruleId', authenticateToken, (req, res) => {
const { condition, action, priority, enabled } = req.body;
const existing = db.prepare('SELECT * FROM character_brain_rules WHERE id = ? AND character_id = ?').get(req.params.ruleId, req.params.id);
if (!existing) return res.status(404).json({ error: 'Brain rule not found' });
db.prepare('UPDATE character_brain_rules SET condition = ?, action = ?, priority = ?, enabled = ? WHERE id = ?')
.run(condition ? JSON.stringify(condition) : existing.condition, action ? JSON.stringify(action) : existing.action, priority ?? existing.priority, enabled !== undefined ? (enabled ? 1 : 0) : existing.enabled, req.params.ruleId);
const rule = db.prepare('SELECT * FROM character_brain_rules WHERE id = ?').get(req.params.ruleId);
res.json({ brain_rule: rule });
});
router.delete('/:id/brain-rules/:ruleId', authenticateToken, (req, res) => {
const existing = db.prepare('SELECT * FROM character_brain_rules WHERE id = ? AND character_id = ?').get(req.params.ruleId, req.params.id);
if (!existing) return res.status(404).json({ error: 'Brain rule not found' });
db.prepare('DELETE FROM character_brain_rules WHERE id = ?').run(req.params.ruleId);
res.json({ message: 'Brain rule deleted' });
});
// --- Simulation ---
router.post('/:id/simulate', authenticateToken, (req, res) => {
const { steps = 10, events = [] } = req.body;
const needs = db.prepare('SELECT * FROM character_needs WHERE character_id = ? AND enabled = 1').all(req.params.id);
const rules = db.prepare('SELECT * FROM character_brain_rules WHERE character_id = ? AND enabled = 1 ORDER BY priority DESC').all(req.params.id);
const simulation = [];
let currentValues = {};
needs.forEach(n => { currentValues[n.name] = { ...n }; });
for (let step = 0; step < steps; step++) {
for (const need of needs) {
if (currentValues[need.name]) {
let newVal = currentValues[need.name].current_value || need.initial_value;
newVal -= need.decay_rate;
newVal = Math.max(need.min_value, Math.min(need.max_value, newVal));
currentValues[need.name].current_value = newVal;
}
}
for (const event of events) {
for (const need of needs) {
if (event[need.name]) {
let val = currentValues[need.name].current_value || need.initial_value;
val += event[need.name];
val = Math.max(need.min_value, Math.min(need.max_value, val));
currentValues[need.name].current_value = val;
}
}
}
const triggeredRules = [];
for (const rule of rules) {
const cond = JSON.parse(rule.condition);
const needVal = currentValues[cond.need]?.current_value ?? need.initial_value;
if (cond.operator === 'lt' && needVal < cond.value) {
triggeredRules.push(rule);
} else if (cond.operator === 'gt' && needVal > cond.value) {
triggeredRules.push(rule);
} else if (cond.operator === 'eq' && needVal === cond.value) {
triggeredRules.push(rule);
} else if (cond.operator === 'lte' && needVal <= cond.value) {
triggeredRules.push(rule);
} else if (cond.operator === 'gte' && needVal >= cond.value) {
triggeredRules.push(rule);
}
}
const snapshot = {};
for (const need of needs) {
snapshot[need.name] = Math.round((currentValues[need.name].current_value || need.initial_value) * 100) / 100;
}
simulation.push({ step, values: snapshot, triggered_rules: triggeredRules.map(r => ({ id: r.id, action: JSON.parse(r.action) })) });
}
res.json({ simulation, final_values: simulation[simulation.length - 1]?.values });
});
export default router;