const express = require('express'); const log = require("../logger.js"); const db = global.db; const router = express.Router(); const expressWs = require('express-ws')(router); router.use((req, res, next) => { if (!req.session.user) return res.redirect('/login?err=4'); const perms = req.session.user.perms ? JSON.parse(req.session.user.perms) : []; if (perms.includes('*') || perms.includes('acl')) { return next(); } return res.status(403).render('error', { error: 'Forbidden', button: { text: 'Back', action: 'back' } }); }); router.get('/', async (req, res) => { const acl = await db.query('SELECT * FROM ACL'); res.render('acl', { acl, user: req.session.user }); }); router.get('/add', async (req, res) => { // Render form to add new ACL entry doorList = {} await db.query('SHOW COLUMNS FROM ACL;').then(columns => { columns.forEach(col => { if (col.Field !== 'Name' && col.Field !== 'CardNumber' && col.Field !== 'PIN' && col.Field !== 'StartDate' && col.Field !== 'EndDate') { doorList[col.Field] = false; } }); res.render('acl-add', { user: req.session.user, doorList }); }).catch(err => { log.error(`Database error fetching ACL columns: ${err}`); return {}; }); }); router.put('/', async (req, res) => { // Attempt to create new ACL entry. Fail if cardNumber already exists const data = req.body; const cardNumber = new Number(data.CardNumber); if (isNaN(cardNumber) || cardNumber <= 0) { return res.status(400).json({ error: 'Invalid card number' }); } try { const check = await db.query('SELECT * FROM ACL WHERE CardNumber = ?', [cardNumber]); if (check && check.length > 0) { return res.status(409).json({ error: 'Card number already exists' }); } // Name, CardNumber, PIN, StartDate, EndDate, [Doors (will be array of door names from client, for each add that column adn set to 1)] const fields = ['Name', 'CardNumber', 'PIN', 'StartDate', 'EndDate']; const placeholders = ['?', '?', '?', '?', '?']; const values = [ data.Name || "Unknown/Not Set", cardNumber, data.PIN || null, data.StartDate || new Date(), data.EndDate || new Date(new Date().setFullYear(new Date().getFullYear() + 99)) ]; for (const door in data.doors) { if (data.doors.hasOwnProperty(door)) { fields.push(door); placeholders.push('?'); values.push(data.doors[door] ? 1 : 0); } } const sql = `INSERT INTO ACL (${fields.join(', ')}) VALUES (${placeholders.join(', ')})`; db.query(sql, values).then(result => { return res.status(201).json({ message: 'ACL entry created' }); }).catch(err => { log.error(`Database error creating ACL entry: ${err}`); return res.status(500).json({ error: 'Internal server error' }); }); } catch (err) { log.error(`Database error checking ACL entry: ${err}`); return res.status(500).json({ error: 'Internal server error' }); } }); router.get('/edit/:cardNumber', async (req, res) => { // Get ACL entry for editing const cardNumber = parseInt(req.params.cardNumber); if (isNaN(cardNumber) || cardNumber <= 0) { return res.status(400).json({ error: 'Invalid card number' }); } try { const entry = await db.query('SELECT * FROM ACL WHERE CardNumber = ?', [cardNumber]); if (!entry || entry.length === 0) { return res.render('error', { error: 'Card number not found', button: { text: 'Back', action: 'back' } }); } // generate doors object const doors = {} Object.keys(entry[0]).forEach(key => { if (key !== 'id' && key !== 'Name' && key !== 'CardNumber' && key !== 'PIN' && key !== 'StartDate' && key !== 'EndDate') { doors[key] = entry[0][key] === 1; } }); return res.render('acl-edit', { aclEntry: entry[0], doorsList: doors, user: req.session.user }); } catch (err) { log.error(`Database error fetching ACL entry: ${err}`); return res.status(500).render('error', { error: 'Internal server error', button: { text: 'Back', action: 'back' } }); } }); router.patch('/:cardNumber', async (req, res) => { // Update ACL entry. Fail if cardNumber does not exist const cardNumber = parseInt(req.params.cardNumber); if (isNaN(cardNumber) || cardNumber <= 0) { return res.status(400).json({ error: 'Invalid card number' }); } const data = req.body; data.cardNumber = cardNumber; try { const check = await db.query('SELECT * FROM ACL WHERE CardNumber = ?', [cardNumber]); if (!check || check.length === 0) { return res.status(404).json({ error: 'Card number not found' }); } // Name, CardNumber, PIN, StartDate, EndDate, [Doors (will be object of door names : true/false from client, for each add that column adn set to 1)] const fields = []; const values = []; if (data.Name) { fields.push('Name = ?'); values.push(data.Name); } if (data.PIN) { fields.push('PIN = ?'); values.push(data.PIN); } if (data.StartDate) { fields.push('StartDate = ?'); values.push(data.StartDate); } if (data.EndDate) { fields.push('EndDate = ?'); values.push(data.EndDate); } for (const door in data.doors) { if (data.doors.hasOwnProperty(door)) { fields.push(`${door} = ?`); values.push(data.doors[door] ? 1 : 0); } if (fields.length === 0) { return res.status(400).json({ error: 'No fields to update' }); } } values.push(cardNumber); const sql = `UPDATE ACL SET ${fields.join(', ')} WHERE CardNumber = ?`; db.query(sql, values).then(result => { return res.status(200).json({ message: 'ACL entry updated' }); }).catch(err => { log.error(`Database error updating ACL entry: ${err}`); return res.status(500).json({ error: 'Internal server error' }); }); } catch (err) { log.error(`Database error checking ACL entry: ${err}`); return res.status(500).json({ error: 'Internal server error' }); } }); router.get('/delete/:cardNumber', async (req, res) => { // Delete ACL entry. Fail if cardNumber does not exist const cardNumber = parseInt(req.params.cardNumber); if (isNaN(cardNumber) || cardNumber <= 0) { return res.status(400).json({ error: 'Invalid card number' }); } try { const check = await db.query('SELECT * FROM ACL WHERE CardNumber = ?', [cardNumber]); if (!check || check.length === 0) { return res.status(404).json({ error: 'Card number not found' }); } db.query('DELETE FROM ACL WHERE CardNumber = ?', [cardNumber]).then(result => { return res.redirect('/acl'); }).catch(err => { log.error(`Database error deleting ACL entry: ${err}`); return res.status(500).render('error', { error: 'Internal server error', button: { text: 'Back', action: 'back' } }); }); } catch (err) { log.error(`Database error checking ACL entry: ${err}`); return res.status(500).json({ error: 'Internal server error' }); } }); router.get('/bulk-add', async (req, res) => { // Render form to bulk add ACL entries doorList = {} await db.query('SHOW COLUMNS FROM ACL;').then(columns => { columns.forEach(col => { if (col.Field !== 'Name' && col.Field !== 'CardNumber' && col.Field !== 'PIN' && col.Field !== 'StartDate' && col.Field !== 'EndDate') { doorList[col.Field] = false; } }); }).catch(err => { log.error(`Database error fetching ACL columns: ${err}`); return {}; }); res.render('acl-bulk-add', { user: req.session.user, doorList: doorList }); }); router.post('/bulk-add', async (req, res) => { // Process bulk add of ACL entries const data = req.body; if (!Array.isArray(data)) { return res.status(400).json({ error: 'Invalid data format' }); } const results = []; for (const entry of data) { const cardNumber = Number(entry.CardNumber); if (isNaN(cardNumber) || cardNumber <= 0) { results.push({ cardNumber: entry.CardNumber, status: 'error', error: 'Invalid card number' }); continue; } try { const exists = await db.query('SELECT * FROM ACL WHERE CardNumber = ?', [cardNumber]); if (exists && exists.length > 0) { results.push({ cardNumber, status: 'error', error: 'Card number already exists' }); continue; } const fields = ['Name', 'CardNumber', 'StartDate', 'EndDate']; const placeholders = ['?', '?', '?', '?']; const values = [ entry.Name || "Unknown/Not Set", cardNumber, entry.StartDate || new Date(), entry.EndDate || new Date(new Date().setFullYear(new Date().getFullYear() + 99)) ]; if (entry.Doors && typeof entry.Doors === 'object') { for (const door in entry.Doors) { if (entry.Doors.hasOwnProperty(door)) { fields.push(door); placeholders.push('?'); values.push(entry.Doors[door] ? 1 : 0); } } } const sql = `INSERT INTO ACL (${fields.join(', ')}) VALUES (${placeholders.join(', ')})`; await db.query(sql, values); results.push({ cardNumber, status: 'success' }); } catch (err) { log.error(`Bulk add error for card ${cardNumber}: ${err}`); results.push({ cardNumber, status: 'error', error: 'Internal server error' }); } } return res.status(200).json({ results }); }); router.ws("/bulk-add", (ws, req) => { log.debug(`Client ${req.sessionID} connected to ACL bulk add WebSocket`); if (!req.session.user) { ws.send(JSON.stringify({ error: 'Not authenticated' })) ws.close(); return; } global.dbEvent.on('event', (event) => { if ( event.Type != 1 || event.Granted != 0 ) return; // Only process 'Swipe' events that were denied ws.send(JSON.stringify(event)); }); }); module.exports = router;