sync rules
This commit is contained in:
158
server.js
158
server.js
@@ -65,6 +65,20 @@ db.exec(`
|
||||
)
|
||||
`);
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS rules (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
enabled INTEGER DEFAULT 1,
|
||||
trigger_type TEXT NOT NULL,
|
||||
trigger_data TEXT NOT NULL,
|
||||
action_type TEXT NOT NULL,
|
||||
action_data TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
@@ -72,6 +86,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 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 (?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
const updateRule = db.prepare(`
|
||||
UPDATE rules SET name = ?, enabled = ?, trigger_type = ?, trigger_data = ?, action_type = ?, action_data = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`);
|
||||
const deleteRule = db.prepare('DELETE FROM rules WHERE id = ?');
|
||||
|
||||
// --- AC INFINITY API LOGIC ---
|
||||
let token = null;
|
||||
|
||||
@@ -269,6 +296,137 @@ app.get('/api/auth/me', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// --- AUTH MIDDLEWARE ---
|
||||
function optionalAuth(req, res, next) {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader && authHeader.startsWith('Bearer ')) {
|
||||
try {
|
||||
const token = authHeader.split(' ')[1];
|
||||
req.user = jwt.verify(token, JWT_SECRET);
|
||||
} catch (e) {
|
||||
req.user = null;
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
function requireAuth(req, res, next) {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'Authentication required' });
|
||||
}
|
||||
try {
|
||||
const token = authHeader.split(' ')[1];
|
||||
req.user = jwt.verify(token, JWT_SECRET);
|
||||
next();
|
||||
} catch (e) {
|
||||
return res.status(401).json({ error: 'Invalid or expired token' });
|
||||
}
|
||||
}
|
||||
|
||||
function requireAdmin(req, res, next) {
|
||||
if (!req.user || req.user.role !== 'admin') {
|
||||
return res.status(403).json({ error: 'Admin access required' });
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
// --- RULES API ---
|
||||
// Helper to format rule for API response
|
||||
function formatRule(row) {
|
||||
const trigger = JSON.parse(row.trigger_data);
|
||||
const action = JSON.parse(row.action_data);
|
||||
|
||||
// Add type back for legacy/action compatibility
|
||||
if (action.type === undefined && row.action_type) {
|
||||
action.type = row.action_type;
|
||||
}
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
enabled: row.enabled === 1,
|
||||
trigger,
|
||||
action
|
||||
};
|
||||
}
|
||||
|
||||
// GET /api/rules - public (guests can view)
|
||||
app.get('/api/rules', (req, res) => {
|
||||
try {
|
||||
const rows = getAllRules.all();
|
||||
res.json(rows.map(formatRule));
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/rules - admin only
|
||||
app.post('/api/rules', requireAuth, requireAdmin, (req, res) => {
|
||||
try {
|
||||
const { name, enabled, trigger, action } = req.body;
|
||||
if (!name || !trigger || !action) {
|
||||
return res.status(400).json({ error: 'name, trigger, and action required' });
|
||||
}
|
||||
|
||||
// Determine trigger type for storage (combined, time, sensor)
|
||||
let triggerType = 'combined';
|
||||
if (trigger.timeRange && !trigger.sensors?.length) triggerType = 'time';
|
||||
if (!trigger.timeRange && trigger.sensors?.length) triggerType = 'sensor';
|
||||
|
||||
const triggerData = JSON.stringify(trigger);
|
||||
const actionData = JSON.stringify(action);
|
||||
const result = insertRule.run(name, enabled ? 1 : 0, triggerType, triggerData, action.type, actionData);
|
||||
|
||||
const newRule = getRuleById.get(result.lastInsertRowid);
|
||||
res.status(201).json(formatRule(newRule));
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/rules/:id - admin only
|
||||
app.put('/api/rules/:id', requireAuth, requireAdmin, (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const existing = getRuleById.get(id);
|
||||
if (!existing) {
|
||||
return res.status(404).json({ error: 'Rule not found' });
|
||||
}
|
||||
|
||||
const { name, enabled, trigger, action } = req.body;
|
||||
|
||||
// Determine trigger type for storage
|
||||
let triggerType = 'combined';
|
||||
if (trigger.timeRange && !trigger.sensors?.length) triggerType = 'time';
|
||||
if (!trigger.timeRange && trigger.sensors?.length) triggerType = 'sensor';
|
||||
|
||||
const triggerData = JSON.stringify(trigger);
|
||||
const actionData = JSON.stringify(action);
|
||||
updateRule.run(name, enabled ? 1 : 0, triggerType, triggerData, action.type, actionData, id);
|
||||
|
||||
const updated = getRuleById.get(id);
|
||||
res.json(formatRule(updated));
|
||||
} 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 {
|
||||
const { id } = req.params;
|
||||
const existing = getRuleById.get(id);
|
||||
if (!existing) {
|
||||
return res.status(404).json({ error: 'Rule not found' });
|
||||
}
|
||||
deleteRule.run(id);
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// API: Devices
|
||||
app.get('/api/devices', (req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user