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;