u
This commit is contained in:
166
uiserver/api/rules.js
Normal file
166
uiserver/api/rules.js
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Rules API - CRUD for automation rules
|
||||
*/
|
||||
|
||||
module.exports = function setupRulesApi(app, { db, checkAuth, requireAdmin, runRules, activeRuleIds }) {
|
||||
// Apply checkAuth middleware to rules routes
|
||||
app.use('/api/rules', checkAuth);
|
||||
|
||||
// GET /api/rules/status - Get currently active rule IDs
|
||||
app.get('/api/rules/status', (req, res) => {
|
||||
res.json({ activeIds: Array.from(activeRuleIds) });
|
||||
});
|
||||
|
||||
// GET /api/rules - List all rules
|
||||
app.get('/api/rules', (req, res) => {
|
||||
try {
|
||||
if (!db) throw new Error('Database not connected');
|
||||
const stmt = db.prepare('SELECT * FROM rules ORDER BY position ASC, id ASC');
|
||||
const rows = stmt.all();
|
||||
const rules = rows.map(row => ({
|
||||
...row,
|
||||
conditions: JSON.parse(row.conditions || '{}'),
|
||||
action: JSON.parse(row.action || '{}')
|
||||
}));
|
||||
res.json(rules);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/rules - Create rule (admin only)
|
||||
app.post('/api/rules', requireAdmin, (req, res) => {
|
||||
const { name, type = 'static', enabled = 1, conditions, action } = req.body;
|
||||
if (!name || !conditions || !action) {
|
||||
return res.status(400).json({ error: 'Missing required fields: name, conditions, action' });
|
||||
}
|
||||
try {
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO rules (name, type, enabled, conditions, action, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
const info = stmt.run(
|
||||
name,
|
||||
type,
|
||||
enabled ? 1 : 0,
|
||||
JSON.stringify(conditions),
|
||||
JSON.stringify(action),
|
||||
req.user?.id || null
|
||||
);
|
||||
runRules(); // Trigger rules immediately
|
||||
global.insertChangelog(req.user?.username || 'admin', `Created rule "${name}"`);
|
||||
res.json({ id: info.lastInsertRowid, name, type, enabled, conditions, action });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/rules/:id - Update rule (admin only)
|
||||
app.put('/api/rules/:id', requireAdmin, (req, res) => {
|
||||
const { name, type, enabled, conditions, action } = req.body;
|
||||
try {
|
||||
// Get old rule for comparison
|
||||
const oldRule = db.prepare('SELECT * FROM rules WHERE id = ?').get(req.params.id);
|
||||
if (!oldRule) {
|
||||
return res.status(404).json({ error: 'Rule not found' });
|
||||
}
|
||||
|
||||
const stmt = db.prepare(`
|
||||
UPDATE rules SET name = ?, type = ?, enabled = ?, conditions = ?, action = ?, updated_at = datetime('now')
|
||||
WHERE id = ?
|
||||
`);
|
||||
const info = stmt.run(
|
||||
name,
|
||||
type || 'static',
|
||||
enabled ? 1 : 0,
|
||||
JSON.stringify(conditions),
|
||||
JSON.stringify(action),
|
||||
req.params.id
|
||||
);
|
||||
|
||||
if (info.changes > 0) {
|
||||
runRules(); // Trigger rules immediately
|
||||
|
||||
// Build detailed changelog
|
||||
const changes = [];
|
||||
if (oldRule.name !== name) {
|
||||
changes.push(`name: "${oldRule.name}" → "${name}"`);
|
||||
}
|
||||
if (!!oldRule.enabled !== !!enabled) {
|
||||
changes.push(`enabled: ${oldRule.enabled ? 'on' : 'off'} → ${enabled ? 'on' : 'off'}`);
|
||||
}
|
||||
const oldConditions = oldRule.conditions || '{}';
|
||||
const newConditions = JSON.stringify(conditions);
|
||||
if (oldConditions !== newConditions) {
|
||||
changes.push('conditions changed');
|
||||
}
|
||||
const oldAction = oldRule.action || '{}';
|
||||
const newAction = JSON.stringify(action);
|
||||
if (oldAction !== newAction) {
|
||||
try {
|
||||
const oldA = JSON.parse(oldAction);
|
||||
const newA = action;
|
||||
if (oldA.channel !== newA.channel) {
|
||||
changes.push(`action channel: ${oldA.channel} → ${newA.channel}`);
|
||||
}
|
||||
if (JSON.stringify(oldA.value) !== JSON.stringify(newA.value)) {
|
||||
changes.push(`action value: ${JSON.stringify(oldA.value)} → ${JSON.stringify(newA.value)}`);
|
||||
}
|
||||
} catch (e) {
|
||||
changes.push('action changed');
|
||||
}
|
||||
}
|
||||
|
||||
const changeText = changes.length > 0
|
||||
? `Updated rule "${name}": ${changes.join(', ')}`
|
||||
: `Updated rule "${name}" (no changes)`;
|
||||
global.insertChangelog(req.user?.username || 'admin', changeText);
|
||||
|
||||
res.json({ id: req.params.id, name, type, enabled, conditions, action });
|
||||
} else {
|
||||
res.status(404).json({ error: 'Rule not found' });
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/rules/:id - Delete rule (admin only)
|
||||
app.delete('/api/rules/:id', requireAdmin, (req, res) => {
|
||||
try {
|
||||
const stmt = db.prepare('DELETE FROM rules WHERE id = ?');
|
||||
const ruleName = db.prepare('SELECT name FROM rules WHERE id = ?').get(req.params.id)?.name || 'Unknown Rule';
|
||||
const info = stmt.run(req.params.id);
|
||||
if (info.changes > 0) {
|
||||
runRules(); // Trigger rules immediately
|
||||
global.insertChangelog(req.user?.username || 'admin', `Deleted rule "${ruleName}" (ID: ${req.params.id})`);
|
||||
res.json({ success: true });
|
||||
} else {
|
||||
res.status(404).json({ error: 'Rule not found' });
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/rules/reorder - Reorder rules (admin only)
|
||||
app.post('/api/rules/reorder', requireAdmin, (req, res) => {
|
||||
const { order } = req.body;
|
||||
if (!Array.isArray(order)) return res.status(400).json({ error: 'Invalid format' });
|
||||
|
||||
const updateStmt = db.prepare('UPDATE rules SET position = ? WHERE id = ?');
|
||||
const updateMany = db.transaction((items) => {
|
||||
for (const item of items) {
|
||||
updateStmt.run(item.position, item.id);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
updateMany(order);
|
||||
runRules(); // Trigger rules immediately
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user