This commit is contained in:
sebseb7
2025-12-21 03:36:29 +01:00
parent 096fc2aa72
commit eab4241e6e
4 changed files with 558 additions and 112 deletions

View File

@@ -74,11 +74,21 @@ db.exec(`
trigger_data TEXT NOT NULL,
action_type TEXT NOT NULL,
action_data TEXT NOT NULL,
sort_order INTEGER DEFAULT 0,
color_tag TEXT DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// Migration: Add sort_order and color_tag if they don't exist
try {
db.exec('ALTER TABLE rules ADD COLUMN sort_order INTEGER DEFAULT 0');
} catch (e) { /* column already exists */ }
try {
db.exec('ALTER TABLE rules ADD COLUMN color_tag TEXT DEFAULT NULL');
} catch (e) { /* column already exists */ }
const insertStmt = db.prepare(`
INSERT INTO readings (dev_id, dev_name, port, port_name, temp_c, humidity, vpd, fan_speed, on_speed, off_speed)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -87,17 +97,19 @@ const insertStmt = db.prepare(`
const getUserByUsername = db.prepare('SELECT * FROM users WHERE username = ?');
// Rules prepared statements
const getAllRules = db.prepare('SELECT * FROM rules ORDER BY id');
const getAllRules = db.prepare('SELECT * FROM rules ORDER BY sort_order, id');
const getRuleById = db.prepare('SELECT * FROM rules WHERE id = ?');
const insertRule = db.prepare(`
INSERT INTO rules (name, enabled, trigger_type, trigger_data, action_type, action_data)
VALUES (?, ?, ?, ?, ?, ?)
INSERT INTO rules (name, enabled, trigger_type, trigger_data, action_type, action_data, sort_order, color_tag)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`);
const updateRule = db.prepare(`
UPDATE rules SET name = ?, enabled = ?, trigger_type = ?, trigger_data = ?, action_type = ?, action_data = ?, updated_at = CURRENT_TIMESTAMP
UPDATE rules SET name = ?, enabled = ?, trigger_type = ?, trigger_data = ?, action_type = ?, action_data = ?, color_tag = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`);
const updateRuleOrder = db.prepare('UPDATE rules SET sort_order = ? WHERE id = ?');
const deleteRule = db.prepare('DELETE FROM rules WHERE id = ?');
const getMaxSortOrder = db.prepare('SELECT COALESCE(MAX(sort_order), 0) as max_order FROM rules');
// --- AC INFINITY API LOGIC ---
let token = null;
@@ -342,10 +354,23 @@ function formatRule(row) {
action.type = row.action_type;
}
// Parse colorTags (stored as JSON array)
let colorTags = [];
if (row.color_tag) {
try {
colorTags = JSON.parse(row.color_tag);
} catch (e) {
// Backwards compat: single tag as string
colorTags = [row.color_tag];
}
}
return {
id: row.id,
name: row.name,
enabled: row.enabled === 1,
sortOrder: row.sort_order || 0,
colorTags,
trigger,
action
};
@@ -364,7 +389,7 @@ app.get('/api/rules', (req, res) => {
// POST /api/rules - admin only
app.post('/api/rules', requireAuth, requireAdmin, (req, res) => {
try {
const { name, enabled, trigger, action } = req.body;
const { name, enabled, trigger, action, colorTags } = req.body;
if (!name || !trigger || !action) {
return res.status(400).json({ error: 'name, trigger, and action required' });
}
@@ -374,9 +399,14 @@ app.post('/api/rules', requireAuth, requireAdmin, (req, res) => {
if (trigger.timeRange && !trigger.sensors?.length) triggerType = 'time';
if (!trigger.timeRange && trigger.sensors?.length) triggerType = 'sensor';
// Get next sort order
const maxOrder = getMaxSortOrder.get().max_order;
const sortOrder = maxOrder + 1;
const triggerData = JSON.stringify(trigger);
const actionData = JSON.stringify(action);
const result = insertRule.run(name, enabled ? 1 : 0, triggerType, triggerData, action.type, actionData);
const colorTagsData = colorTags?.length ? JSON.stringify(colorTags) : null;
const result = insertRule.run(name, enabled ? 1 : 0, triggerType, triggerData, action.type, actionData, sortOrder, colorTagsData);
const newRule = getRuleById.get(result.lastInsertRowid);
res.status(201).json(formatRule(newRule));
@@ -394,7 +424,7 @@ app.put('/api/rules/:id', requireAuth, requireAdmin, (req, res) => {
return res.status(404).json({ error: 'Rule not found' });
}
const { name, enabled, trigger, action } = req.body;
const { name, enabled, trigger, action, colorTags } = req.body;
// Determine trigger type for storage
let triggerType = 'combined';
@@ -403,7 +433,8 @@ app.put('/api/rules/:id', requireAuth, requireAdmin, (req, res) => {
const triggerData = JSON.stringify(trigger);
const actionData = JSON.stringify(action);
updateRule.run(name, enabled ? 1 : 0, triggerType, triggerData, action.type, actionData, id);
const colorTagsData = colorTags?.length ? JSON.stringify(colorTags) : null;
updateRule.run(name, enabled ? 1 : 0, triggerType, triggerData, action.type, actionData, colorTagsData, id);
const updated = getRuleById.get(id);
res.json(formatRule(updated));
@@ -412,6 +443,26 @@ app.put('/api/rules/:id', requireAuth, requireAdmin, (req, res) => {
}
});
// PUT /api/rules/reorder - admin only (reorder rules)
app.put('/api/rules/reorder', requireAuth, requireAdmin, (req, res) => {
try {
const { ruleIds } = req.body; // Array of rule IDs in new order
if (!Array.isArray(ruleIds)) {
return res.status(400).json({ error: 'ruleIds array required' });
}
// Update sort_order for each rule
ruleIds.forEach((ruleId, index) => {
updateRuleOrder.run(index, ruleId);
});
const rows = getAllRules.all();
res.json(rows.map(formatRule));
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// DELETE /api/rules/:id - admin only
app.delete('/api/rules/:id', requireAuth, requireAdmin, (req, res) => {
try {