From af1eaa3a57d96303b1fcce2962731c3ccac1eb4a Mon Sep 17 00:00:00 2001 From: ChrisChrome Date: Mon, 27 Oct 2025 08:04:46 -0600 Subject: [PATCH] Add directory API endpoints --- index.js | 88 +++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 31 +++-------------- package.json | 2 +- 3 files changed, 93 insertions(+), 28 deletions(-) diff --git a/index.js b/index.js index 962cfa0..a074936 100644 --- a/index.js +++ b/index.js @@ -708,6 +708,94 @@ app.delete('/api/v1/user/directory/:number', (req, res) => { // Delete a directo }); }); +// User directory management via API key, for automated scripts +app.post('/api/v1/user/dir/newEntry', async (req, res) => { + const apiKey = req.headers['authorization'] ? req.headers['authorization'].replace('Bearer ', '') : null; + if (!apiKey) { + res.status(401).json({ error: 'API Key is required!' }); + return; + } + + const routeData = await pool.query("SELECT * FROM routes WHERE apiKey = ?", [apiKey]); + if (!routeData || routeData.length === 0) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + + const route = routeData[0]; + var number = Number(req.body.number); + var name = String(req.body.name); + if (!number || !name) { + res.status(400).json({ error: 'Bad Request' }); + return; + } + if (number < route.block_start || number > route.block_start + route.block_length) { + res.status(403).json({ error: 'Forbidden' }); + return; + } + + // Remove html + name = require("escape-html")(name); + // If number already exists, update, otherwise insert + pool.query('SELECT * FROM directory WHERE number = ? AND route = ?', [number, route.id]).then((rows) => { + const row = rows[0]; + if (row) { + pool.query('UPDATE directory SET name = ? WHERE number = ? AND route = ?', + [name, number, route.id]).then(() => { + res.json({ message: 'Updated' }); + } + ).catch(err => { + console.error('Error updating directory entry:', err); + res.status(500).json({ error: 'Internal server error' }); + }); + } else { + pool.query('INSERT INTO directory (number, name, route) VALUES (?, ?, ?)', + [number, name, route.id]).then(() => { + res.status(201).json({ message: 'Created' }); + } + ).catch(err => { + console.error('Error creating directory entry:', err); + res.status(500).json({ error: 'Internal server error' }); + }); + } + }).catch(err => { + console.error('Error checking for existing directory entry:', err); + res.status(500).json({ error: 'Internal server error' }); + }); +}); + +app.delete('/api/v1/user/dir/deleteEntry/:number', async (req, res) => { + const apiKey = req.headers['authorization'] ? req.headers['authorization'].replace('Bearer ', '') : null; + if (!apiKey) { + res.status(401).json({ error: 'API Key is required!' }); + return; + } + + const routeData = await pool.query("SELECT * FROM routes WHERE apiKey = ?", [apiKey]); + if (!routeData || routeData.length === 0) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + + const route = routeData[0]; + const number = Number(req.params.number); + if (!number) { + res.status(400).json({ error: 'Bad Request' }); + return; + } + // Check that the number is within the block range for the current user + if (number < route.block_start || number > route.block_start + route.block_length) { + res.status(403).json({ error: 'Forbidden' }); + return; + } + pool.query('DELETE FROM directory WHERE number = ? AND route = ?', [number, route.id]).then(() => { + res.status(200).json({ message: 'Deleted' }); + }).catch(err => { + console.error('Error deleting directory entry:', err); + res.status(500).json({ error: 'Internal server error' }); + }); +}); + // == END USER ROUTES == // == Directory routes == (unauthenticated) diff --git a/package-lock.json b/package-lock.json index 9aa4c72..86bffe7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "bcrypt": "^5.1.1", "connect-sqlite": "^0.0.1", - "dotenv": "^16.4.7", + "dotenv": "^16.6.1", "ejs": "^3.1.10", "escape-html": "^1.0.3", "express": "^4.21.2", @@ -734,9 +734,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -795,29 +795,6 @@ "node": ">= 0.8" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", diff --git a/package.json b/package.json index 626416a..9dd9cc5 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "bcrypt": "^5.1.1", "connect-sqlite": "^0.0.1", - "dotenv": "^16.4.7", + "dotenv": "^16.6.1", "ejs": "^3.1.10", "escape-html": "^1.0.3", "express": "^4.21.2",