From 2f849f63e7a7aa9ceced1e71523073acc4cf0f4e Mon Sep 17 00:00:00 2001 From: ChrisChrome Date: Sun, 16 Feb 2025 09:30:34 -0700 Subject: [PATCH] Start work on rewrite/Cleanup --- analytics.js | 60 ++++ index.js | 719 ++-------------------------------------------- package-lock.json | 526 ++++++++++++--------------------- package.json | 6 +- routes/admin.js | 89 ++++++ routes/api.js | 516 +++++++++++++++++++++++++++++++++ routes/user.js | 58 ++++ 7 files changed, 924 insertions(+), 1050 deletions(-) create mode 100644 analytics.js create mode 100644 routes/admin.js create mode 100644 routes/api.js create mode 100644 routes/user.js diff --git a/analytics.js b/analytics.js new file mode 100644 index 0000000..5395086 --- /dev/null +++ b/analytics.js @@ -0,0 +1,60 @@ +const pool = global.db_pool; +if (!pool) { + throw new Error('Database pool is not defined'); +} + +const addAnalytic = (tag) => { + pool.query("SELECT * FROM analytics WHERE tag = ?", [tag]).then((rows) => { + if (rows.length === 0) { + conn.query("INSERT INTO analytics (tag, count) VALUES (?, 1)", [tag]).catch(err => { + console.error('Error creating analytics:', err); + }); + } else { + conn.query("UPDATE analytics SET count = count + 1 WHERE tag = ?", [tag]).catch(err => { + console.error('Error updating analytics:', err); + }); + } + }).catch(err => { + console.error('Error checking analytics:', err); + }).finally(() => { + conn.release(); + }); +} + +const dailyAnalytic = (tag) => { // This is a bit more complex, but it's just a daily count + // check if the tag, and tag_date exists. If the date is not today reset count to 0 and date to today + // If the date is today, increment count + const date = new Date(); + const today = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`; + + pool.query("SELECT * FROM dailyAnalytics WHERE tag = ? AND tag_date = ?", [tag, today]).then((rows) => { + if (rows.length === 0) { + conn.query("INSERT INTO dailyAnalytics (tag, tag_date, count) VALUES (?, ?, 1)", [tag, today]).catch(err => { + console.error('Error creating daily analytics:', err); + }); + } else { + conn.query("UPDATE dailyAnalytics SET count = count + 1 WHERE tag = ? AND tag_date = ?", [tag, today]).catch(err => { + console.error('Error updating daily analytics:', err); + }); + } + }).catch(err => { + console.error('Error checking daily analytics:', err); + }).finally(() => { + conn.release(); + }); +} + +const logCall = (caller, callee) => { + pool.query('INSERT INTO callLogs (caller, callee, timestamp) VALUES (?, ?, ?)', + [caller, callee, Math.floor(Date.now())]).catch(err => { + console.error('Error logging call:', err); + }).finally(() => { + conn.release(); + }); +} + +module.exports = { + addAnalytic, + dailyAnalytic, + logCall +} \ No newline at end of file diff --git a/index.js b/index.js index 88f3078..ea2a798 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,8 @@ const mariadb = require('mariadb'); const bcrypt = require("bcrypt") const crypto = require("crypto") const app = express(); +const path = require('path'); +const fs = require('fs'); const port = process.env.SERVER_PORT || 3000; const pool = mariadb.createPool({ @@ -33,6 +35,11 @@ const pool = mariadb.createPool({ }); const saltRounds = 10; +global.db_pool = pool; // Make the pool global so we can use it in other files + +const analytics = require("./analytics"); + +// Run migrations and reset admin user if desired pool.getConnection().then((conn) => { require("./migrations")(pool).then(() => { conn.query("SELECT * FROM users WHERE id = 1").then((row) => { @@ -80,537 +87,14 @@ app.set('views', __dirname + '/views'); // Static files app.use(express.static('public')); -const addAnalytic = (tag) => { - pool.getConnection().then(conn => { - conn.query("SELECT * FROM analytics WHERE tag = ?", [tag]).then((rows) => { - if (rows.length === 0) { - conn.query("INSERT INTO analytics (tag, count) VALUES (?, 1)", [tag]).catch(err => { - console.error('Error creating analytics:', err); - }); - } else { - conn.query("UPDATE analytics SET count = count + 1 WHERE tag = ?", [tag]).catch(err => { - console.error('Error updating analytics:', err); - }); - } - }).catch(err => { - console.error('Error checking analytics:', err); - }).finally(() => { - conn.release(); - }); - }).catch(err => { - console.error('Error getting connection:', err); - }); -} - -const dailyAnalytic = (tag) => { // This is a bit more complex, but it's just a daily count - // check if the tag, and tag_date exists. If the date is not today reset count to 0 and date to today - // If the date is today, increment count - const date = new Date(); - const today = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`; - - pool.getConnection().then(conn => { - conn.query("SELECT * FROM dailyAnalytics WHERE tag = ? AND tag_date = ?", [tag, today]).then((rows) => { - if (rows.length === 0) { - conn.query("INSERT INTO dailyAnalytics (tag, tag_date, count) VALUES (?, ?, 1)", [tag, today]).catch(err => { - console.error('Error creating daily analytics:', err); - }); - } else { - conn.query("UPDATE dailyAnalytics SET count = count + 1 WHERE tag = ? AND tag_date = ?", [tag, today]).catch(err => { - console.error('Error updating daily analytics:', err); - }); - } - }).catch(err => { - console.error('Error checking daily analytics:', err); - }).finally(() => { - conn.release(); - }); - }).catch(err => { - console.error('Error getting connection:', err); - }); -} app.use((req, res, next) => { if (req.path.startsWith("/api/v1")) { - addAnalytic("apiCalls"); + analytics.addAnalytic("apiCalls"); }; next(); }) -// Admin routes - -// admin/logout -app.get('/admin/logout', (req, res) => { - req.session.destroy(); - res.redirect('/admin/login'); -}); - -app.get('/admin/login', (req, res) => { - res.render('admin/login'); -}); - -app.get('/admin', (req, res) => { - if (!req.session.adminAuthenticated) { - res.redirect('/admin/login'); - return; - } - res.render('admin/index', { user: req.session.user }); -}); - -app.get('/admin/create', (req, res) => { - if (!req.session.adminAuthenticated) { - res.redirect('/admin/login'); - return; - } - res.render('admin/create', { user: req.session.user }); -}); - -app.get('/admin/route/:id', (req, res) => { - if (!req.session.adminAuthenticated) { - res.redirect('/admin/login'); - return; - } - pool.getConnection().then(conn => { - conn.query('SELECT * FROM routes WHERE id = ?', [req.params.id]).then((rows) => { - const row = rows[0]; - if (!row) { - res.status(404).send('Not Found'); - return; - } - res.render('admin/edit', { user: req.session.user, data: row }); - }).catch(err => { - console.error('Error getting route:', err); - res.status(500).send('Internal server error'); - }).finally(() => { - conn.release(); - }); - }); -}); - -app.post('/admin/login', (req, res) => { - const username = req.body.username; - const password = req.body.password; - pool.getConnection().then(conn => { - conn.query("SELECT * FROM users WHERE username = ?", [String(username)]).then((rows) => { - const row = rows[0]; - if (!row) { - res.status(401).send('Unauthorized (Not Found)'); - return; - } - bcrypt.compare(password, row.passwordHash, (err, result) => { - if (err) { - console.error('Error comparing password:', err); - res.status(500).send('Internal server error'); - return; - } - if (result) { - req.session.adminAuthenticated = true; - req.session.user = row.username; - res.redirect('/admin'); - } else { - res.status(401).send('Unauthorized'); - } - }); - }).catch(err => { - console.error('Error getting user:', err); - res.status(500).send('Internal server error'); - }).finally(() => { - conn.release(); - }); - }); -}) - -app.get('/api/v1/admin/routes', (req, res) => { // Get all routes - if (!req.session.adminAuthenticated) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - pool.getConnection().then(conn => { - conn.query('SELECT * FROM routes').then((rows) => { - res.json(rows); - }).catch(err => { - console.error('Error getting routes:', err); - res.status(500).json({ error: 'Internal server error' }); - }).finally(() => { - conn.release(); - }); - }); -}); - -app.get('/api/v1/admin/route/:id', (req, res) => { // Get route - if (!req.session.adminAuthenticated) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - pool.getConnection().then(conn => { - conn.query('SELECT * FROM routes WHERE id = ?', [req.params.id]).then((rows) => { - const row = rows[0]; - if (!row) { - res.status(404).json({ error: 'Not Found' }); - return; - } - res.json(row); - }).catch(err => { - console.error('Error getting route:', err); - res.status(500).json({ error: 'Internal server error' }); - }).finally(() => { - conn.release(); - }); - }); -}); - -app.post('/api/v1/admin/route', (req, res) => { // Create a new route - if (!req.session.adminAuthenticated) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - const server = req.body.server; - const port = req.body.port; - const auth = req.body.auth || "from-astrocom"; - const secret = req.body.secret || crypto.randomBytes(15).toString('hex'); - const block_start = req.body.block_start; - const block_length = req.body.block_length || 9999; - const apiKey = crypto.randomBytes(32).toString('hex'); - const contact = req.body.contact || "Unknown"; - // Validate all inputs exist - if (!server || !port || !block_start) { - res.status(400).json({ error: 'Bad Request' }); - return; - } - // Check if route already exists (OR conditions on server, and block range) - pool.getConnection().then(conn => { - conn.query('SELECT * FROM routes WHERE block_start <= ? AND block_start + block_length >= ?', [block_start, block_start]).then((rows) => { - const row = rows[0]; - if (row) { - res.status(409).json({ error: 'Conflict' }); - return; - } else { - conn.query('INSERT INTO routes (server, port, auth, secret, block_start, block_length, apiKey, contact) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', - [server, port, auth, secret, block_start, block_length, apiKey, contact]).then(() => { - res.status(201).json({ message: 'Created' }); - }).catch(err => { - console.error('Error creating route:', err); - res.status(500).json({ error: 'Internal server error' }); - }); - } - }).catch(err => { - console.error('Error checking for existing route:', err); - res.status(500).json({ error: 'Internal server error' }); - }).finally(() => { - conn.release(); - }); - }); -}); - -app.put('/api/v1/admin/route/:id', (req, res) => { // Update a route - // Check if authenticated - if (!req.session.adminAuthenticated) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - // Check if route exists - pool.getConnection().then(conn => { - conn.query('SELECT * FROM routes WHERE id = ?', [req.params.id]).then((rows) => { - const row = rows[0]; - if (!row) { - res.status(404).json({ error: 'Not Found' }); - return; - } - // Update route - const server = req.body.server || row.server; - const port = req.body.port || row.port; - const auth = req.body.auth || row.auth; - const secret = req.body.secret || row.secret; - const block_start = req.body.block_start || row.block_start; - const block_length = req.body.block_length || row.block_length; - const contact = req.body.contact || row.contact; - console.log(`Updating ${req.params.id} to ${server}:${port} with ${auth}:${secret} for ${block_start} - ${block_start + block_length}. Contact: ${contact}`); - conn.query('UPDATE routes SET server = ?, port = ?, auth = ?, secret = ?, block_start = ?, block_length = ?, contact = ? WHERE id = ?', - [server, port, auth, secret, block_start, block_length, contact, req.params.id]).then(() => { - res.json({ message: 'Updated' }); - }).catch(err => { - console.error('Error updating route:', err); - res.status(500).json({ error: 'Internal server error' }); - }); - }).catch(err => { - console.error('Error getting route:', err); - res.status(500).json({ error: 'Internal server error' }); - }).finally(() => { - conn.release(); - }); - }); -}); - -app.delete('/api/v1/admin/route/:id', (req, res) => { // Delete a route - if (!req.session.adminAuthenticated) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - pool.getConnection().then(async conn => { - await conn.query('DELETE FROM directory WHERE route = ?', [req.params.id]) - conn.query('DELETE FROM routes WHERE id = ?', [req.params.id]).then(() => { - res.json({ message: 'Deleted' }); - }).catch(err => { - console.error('Error deleting route:', err); - res.status(500).json({ error: 'Internal server error' }); - }).finally(() => { - conn.release(); - }); - }); -}); - -app.delete('/api/v1/admin/directory/:number', (req, res) => { // Delete a directory entry - if (!req.session.adminAuthenticated) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - const number = Number(req.params.number); - if (!number) { - res.status(400).json({ error: 'Bad Request' }); - return; - } - pool.getConnection().then(conn => { - conn.query('DELETE FROM directory WHERE number = ?', [number]).then(() => { - res.status(200).json({ message: 'Deleted' }); - }).catch(err => { - console.error('Error deleting directory entry:', err); - res.status(500).json({ error: 'Internal server error' }); - }).finally(() => { - conn.release(); - }); - }); -}); - -app.get("/api/v1/admin/callLogs", (req, res) => { - if (!req.session.adminAuthenticated) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - // if ?page is set, return 100 results from that page, if no page assume page 1 - const page = Number(req.query.page) || 1; - const offset = (page - 1) * 100; - // Get full count of call logs to calculate total pages - pool.getConnection().then(conn => { - conn.query("SELECT COUNT(*) as count FROM callLogs").then((rows) => { - const totalPages = Math.ceil(rows[0].count / 100); - conn.query("SELECT * FROM callLogs ORDER BY timestamp DESC LIMIT 100 OFFSET ?", [offset]).then((rows) => { - res.json({ totalPages, page, data: rows }); - }).catch(err => { - console.error('Error getting call logs:', err); - res.status(500).json({ error: 'Internal server error' }); - }); - }).catch(err => { - console.error('Error getting call log count:', err); - res.status(500).json({ error: 'Internal server error' }); - }).finally(() => { - conn.release(); - }); - }); -}); - -// == END ADMIN ROUTES == - -// == User routes == // allows someone to log in with their API key and add entries to the Directory (as long as the number is within their block range) -app.get('/user', (req, res) => { - if (!req.session.userAuthenticated) { - res.redirect('/user/login'); - return; - } - res.render('user/index', { user: req.session.user }); -}); - -app.get('/user/login', (req, res) => { - res.render('user/login'); -}); - -app.post('/user/login', (req, res) => { - const apiKey = req.body.apiKey; - pool.getConnection().then(conn => { - conn.query("SELECT * FROM routes WHERE apiKey = ?", [apiKey]).then((rows) => { - const row = rows[0]; - if (!row) { - res.status(401).send('Unauthorized'); - return; - } - req.session.userAuthenticated = true; - req.session.userData = row; - res.redirect('/user'); - }).catch(err => { - console.error('Error getting route:', err); - res.status(500).send('Internal server error'); - }).finally(() => { - conn.release(); - }); - }); -}); - -app.get('/user/logout', (req, res) => { - req.session.destroy(); - res.redirect('/user/login'); -}); - -app.get("/user/edit", (req, res) => { - if (!req.session.userAuthenticated) { - res.redirect('/user/login'); - return; - } - // Remove block_start, block_length, and apiKey from the response - responseData = { - server: req.session.userData.server, - port: req.session.userData.port, - auth: req.session.userData.auth, - secret: req.session.userData.secret - } - res.render('user/edit', { data: responseData }); -}); - -app.get('/api/v1/user/route', (req, res) => { // Get route - if (!req.session.userAuthenticated) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - res.json(req.session.userData); -}); - -app.put('/api/v1/user/route', (req, res) => { // Update route - if (!req.session.userAuthenticated) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - if (!req.session.userData.apiKey) { - req.session.destroy(); // Something weird happened, destroy session - res.status(401).json({ error: 'Unauthorized' }); - return; - } - // Does not allow for ID to be specified, always update current users route - const server = req.body.server || req.session.userData.server; - const port = req.body.port || req.session.userData.port; - const auth = req.body.auth || req.session.userData.auth; - const secret = req.body.secret || req.session.userData.secret; - pool.getConnection().then(conn => { - conn.query('UPDATE routes SET server = ?, port = ?, auth = ?, secret = ? WHERE apiKey = ?', - [server, port, auth, secret, req.session.userData.apiKey]).then(() => { - req.session.userData.server = server; - req.session.userData.port = port; - req.session.userData.auth = auth; - req.session.userData.secret = secret; - res.json({ message: 'Updated' }); - }).catch(err => { - console.error('Error updating route:', err); - res.status(500).json({ error: 'Internal server error' }); - }).finally(() => { - conn.release(); - }); - }); -}); - -app.get('/api/v1/user/directory', (req, res) => { // Get directory entries created by user - if (!req.session.userAuthenticated) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - pool.getConnection().then(conn => { - conn.query('SELECT * FROM directory WHERE route = ?', [req.session.userData.id]).then((rows) => { - res.json(rows); - }).catch(err => { - console.error('Error getting routes:', err); - res.status(500).json({ error: 'Internal server error' }); - }).finally(() => { - conn.release(); - }); - }); -}); - -app.post('/api/v1/user/directory', (req, res) => { // Create a new directory entry - // Check if authenticated - if (!req.session.userAuthenticated) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - // Check that the number is within the block range for the current user - 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 < req.session.userData.block_start || number > req.session.userData.block_start + req.session.userData.block_length) { - res.status(403).json({ error: 'Forbidden' }); - return; - } - - // Remove html - name = require("escape-html")(name); - - const route = req.session.userData.id; - // If number already exists, update, otherwise insert - pool.getConnection().then(conn => { - conn.query('SELECT * FROM directory WHERE number = ? AND route = ?', [number, route]).then((rows) => { - const row = rows[0]; - if (row) { - conn.query('UPDATE directory SET name = ? WHERE number = ? AND route = ?', - [name, number, route]).then(() => { - res.json({ message: 'Updated' }); - }).catch(err => { - console.error('Error updating directory entry:', err); - res.status(500).json({ error: 'Internal server error' }); - }); - } else { - conn.query('INSERT INTO directory (number, name, route) VALUES (?, ?, ?)', - [number, name, route]).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' }); - }).finally(() => { - conn.release(); - }); - }); -}); - -app.delete('/api/v1/user/directory/:number', (req, res) => { // Delete a directory entry - if (!req.session.userAuthenticated) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - const number = Number(req.params.number); - if (!number) { - res.status(400).json({ error: 'Bad Request' }); - return; - } - pool.getConnection().then(conn => { - conn.query('DELETE FROM directory WHERE number = ? AND route = ?', [number, req.session.userData.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' }); - }).finally(() => { - conn.release(); - }); - }); -}); - -// == END USER ROUTES == - -// == Directory routes == (unauthenticated) - -app.get("/api/v1/directory", (req, res) => { - pool.getConnection().then(conn => { - conn.query("SELECT * FROM directory").then((rows) => { - res.json(rows); - }).catch(err => { - console.error('Error getting directory:', err); - res.status(500).json({ error: 'Internal server error' }); - }).finally(() => { - conn.release(); - }); - }); -}); // Other public endpoints that need special handling discordInviteCache = { time: 0, url: "" }; @@ -634,187 +118,24 @@ app.get("/discord", (req, res) => { }); }); -app.get("/api/analytics", (req, res) => { - pool.getConnection().then(conn => { - conn.query("SELECT * FROM analytics").then((total) => { - conn.query("SELECT * FROM dailyAnalytics").then((daily) => { - // Find the latest date and add "current:true" to it - var latest = { tag_date: "1970-01-01", count: 0 }; - daily.forEach((entry) => { - if (entry.tag_date > latest.tag_date) { - latest = entry; - } - }); - latest.current = true; - res.json({ total, daily }); - }).catch(err => { - console.error('Error getting daily analytics:', err); - res.status(500).send('Internal server error'); - }); - }).catch(err => { - console.error('Error getting analytics:', err); - res.status(500).send('Internal server error'); - }).finally(() => { - conn.release(); - }); - }); -}); - app.get("/footer", (req, res) => { res.render("footer", { version }); }); -app.get("/api/v1/checkAvailability/:number", (req, res) => { - // Check if the number is 7 digits - const number = Number(req.params.number); - if (number < 2000000 || number > 9999999) { - res.status(400).json({ error: `Number is outside valid range` }); - return; - } - pool.getConnection().then(conn => { - conn.query('SELECT * FROM routes WHERE block_start <= ? AND block_start + block_length >= ?', [number, number]).then((rows) => { - const row = rows[0]; - if (row) { - res.json({ available: false, block: row.block_start }); - } else { - res.json({ available: true }); - } - }).catch(err => { - console.error('Error getting route:', err); - res.status(500).json({ error: 'Internal server error' }); - }).finally(() => { - conn.release(); - }); +const startup = () => { + // Load all route modules from the 'routes' folder + const routesPath = path.join(__dirname, 'routes'); + fs.readdirSync(routesPath).forEach((file) => { + const route = require(path.join(routesPath, file)); + const routeName = `/${file.replace('.js', '')}`; // Use filename as route base + app.use(routeName, route); + console.log(`Using ${routeName}`) }); -}); -app.get("/api/healthcheck", (req, res) => { - // Check ability to connect to database with select * from routes - pool.getConnection().then(conn => { - conn.query('SELECT * FROM routes').then(() => { - res.status(200).send('OK'); - }).catch(err => { - console.error('Error checking health:', err); - res.status(500).send('Internal server error'); - }).finally(() => { - conn.release(); - }); - }); -}); - -// logCall function (caller, callee) -const logCall = (caller, callee) => { - pool.getConnection().then(conn => { - conn.query('INSERT INTO callLogs (caller, callee, timestamp) VALUES (?, ?, ?)', - [caller, callee, Math.floor(Date.now())]).catch(err => { - console.error('Error logging call:', err); - }).finally(() => { - conn.release(); - }); + // Start server + app.listen(port, () => { + console.log(`Listening on port ${port}`); }); } -// Query to get a route -app.get('/api/v1/route/:apiKey/:ani/:number', (req, res) => { - const apiKey = req.params.apiKey; - const number = Number(req.params.number); - const ani = Number(req.params.ani); - pool.getConnection().then(conn => { - //conn.query("SELECT * FROM routes WHERE apiKey = ? AND block_start <= ? AND block_start + block_length >= ?", [apiKey, ani, ani]).then((rows) => { - conn.query("SELECT * FROM routes WHERE apiKey = ?", [apiKey]).then((rows) => { // We'll try this Nick, if it doesn't work we'll go back to the original - const row = rows[0]; - // If no row or error, return 401 - if (!row) { - res.status(401).send(`${process.env.MSG_ROUTE_ADDRESS}/401`) - return; - } - conn.query('SELECT * FROM routes WHERE block_start <= ? AND block_start + block_length >= ?', [number, number]).then((rows) => { - const row = rows[0]; - if (row) { - // Check if the ANI is within the block range - // If it is, return `local` - console.log(`New Call: ${ani} -> ${number}`); - logCall(ani, number); - // incriment estCallsMade analytics - addAnalytic("estCallsMade"); - dailyAnalytic("dailyCallsMade"); - if (ani >= row.block_start && ani <= row.block_start + row.block_length) { - res.status(200).send('local'); - } else { - res.status(200).send(`IAX2/${row.auth}:${row.secret}@${row.server}:${row.port}/${number}`); - } - } else { - res.status(404).send(`${process.env.MSG_ROUTE_ADDRESS}/404`); - } - }).catch(err => { - console.error('Error getting route:', err); - res.status(500).send(`${process.env.MSG_ROUTE_ADDRESS}/500`) - }); - }).catch(err => { - console.error(err); - res.status(401).send(`${process.env.MSG_ROUTE_ADDRESS}/401`) - }).finally(() => { - conn.release(); - }); - }); -}); - -app.get('/api/v1', (req, res) => { // Backwards compatibility with TandmX cause why not, it's easy - const apiKey = req.query.auth; - const number = Number(req.query.number); - const ani = Number(req.query.ani); - pool.getConnection().then(conn => { - conn.query("SELECT * FROM routes WHERE apiKey = ? AND block_start <= ? AND block_start + block_length >= ?", [apiKey, ani, ani]).then((rows) => { - const row = rows[0]; - // If no row or error, return 401 - if (!row) { - res.status(401).send(`${process.env.MSG_ROUTE_ADDRESS}/401`) - return; - } - conn.query('SELECT * FROM routes WHERE block_start <= ? AND block_start + block_length >= ?', [number, number]).then((rows) => { - const row = rows[0]; - if (row) { - // Check if the ANI is within the block range - // If it is, return `local` - console.log(`New Call: ${ani} -> ${number}`); - logCall(ani, number); - addAnalytic("estCallsMade"); - dailyAnalytic("dailyCallsMade"); - if (ani >= row.block_start && ani <= row.block_start + row.block_length) { - res.status(200).send('local'); - } else { - res.status(200).send(`IAX2/${row.auth}:${row.secret}@${row.server}:${row.port}/${number}`); - } - } else { - res.status(404).send(`${process.env.MSG_ROUTE_ADDRESS}/404`); - } - }).catch(err => { - console.error('Error getting route:', err); - res.status(500).send(`${process.env.MSG_ROUTE_ADDRESS}/500`) - }); - }).catch(err => { - console.error(err); - res.status(401).send(`${process.env.MSG_ROUTE_ADDRESS}/401`) - }).finally(() => { - conn.release(); - }); - }); -}); - -// Management Routes (Like restarting the server) -app.post("/api/v1/manage/restart", (req, res) => { - // Check Authorization header against process.env.MANAGEMENT_AUTH - if (req.headers.authorization !== (process.env.MANAGEMENT_AUTH || crypto.randomBytes(32).toString('hex'))) { // Default to random in case we forget to configure it - res.status(401).send("Unauthorized"); - return; - } - res.status(200).send("Restarting server..."); - setTimeout(() => { - process.exit(0); - }, 500); -}); - -// Start server -app.listen(port, () => { - console.log(`Listening on port ${port}`); -}); \ No newline at end of file +startup(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9aa4c72..d6aebce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,14 +10,12 @@ "license": "ISC", "dependencies": { "bcrypt": "^5.1.1", - "connect-sqlite": "^0.0.1", "dotenv": "^16.4.7", "ejs": "^3.1.10", "escape-html": "^1.0.3", "express": "^4.21.2", "express-session": "^1.18.1", "mariadb": "^3.4.0", - "session-file-store": "^1.5.0", "sqlite3": "^5.1.7" } }, @@ -48,54 +46,6 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, "node_modules/@npmcli/fs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", @@ -133,15 +83,15 @@ } }, "node_modules/@types/geojson": { - "version": "7946.0.15", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.15.tgz", - "integrity": "sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA==", + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.13.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", + "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -202,9 +152,9 @@ "license": "MIT" }, "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", "license": "MIT", "optional": true, "dependencies": { @@ -259,18 +209,17 @@ "license": "ISC" }, "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "deprecated": "This package is no longer supported.", "license": "ISC", - "optional": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=10" } }, "node_modules/array-flatten": { @@ -279,30 +228,12 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "license": "MIT", - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, - "node_modules/bagpipe": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/bagpipe/-/bagpipe-0.3.5.tgz", - "integrity": "sha512-42sAlmPDKes1nLm/aly+0VdaopSU9br+jkRELedhQxI5uXHgtk47I83Mpmf4zoNTRMASdLFtUkimlu/Z9zQ8+g==", - "license": "MIT" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -343,12 +274,6 @@ "node": ">= 10.0.0" } }, - "node_modules/bcrypt/node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", - "license": "MIT" - }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -369,12 +294,6 @@ "readable-stream": "^3.4.0" } }, - "node_modules/bn.js": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", - "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", - "license": "MIT" - }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -472,28 +391,23 @@ "node": ">= 10" } }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "license": "MIT", + "node_modules/cacache/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "optional": true, "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" + "yallist": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10" } }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -504,13 +418,13 @@ } }, "node_modules/call-bound": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.2.tgz", - "integrity": "sha512-0lk0PHFe/uz0vl527fG9CgdE9WdafjDbCXvBbs+LUv000TVt2Jjhqbs4Jwm8gz070w8xXyEAxrPOMullsxXeGg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "get-intrinsic": "^1.2.5" + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -587,17 +501,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, - "node_modules/connect-sqlite": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/connect-sqlite/-/connect-sqlite-0.0.1.tgz", - "integrity": "sha512-75BbN7FUSs8MWxlYoqqhyx0TSKErEzRqKx2o4SIcAR/8H81acKm2Oy1ym3uMz1BCoH7bNmyc2ETVV4hrAK26Pw==", - "dependencies": { - "sqlite": ">= 1.0.4" - }, - "engines": { - "node": ">= 0.1.98" - } - }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -673,23 +576,6 @@ "node": ">=4.0.0" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -746,12 +632,12 @@ } }, "node_modules/dunder-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", - "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", + "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" }, @@ -863,9 +749,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -1056,20 +942,6 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -1098,42 +970,42 @@ } }, "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", "deprecated": "This package is no longer supported.", "license": "ISC", - "optional": true, "dependencies": { "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" + "wide-align": "^1.1.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=10" } }, "node_modules/get-intrinsic": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", - "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", + "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -1142,6 +1014,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -1185,7 +1070,8 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/has-flag": { "version": "4.0.0", @@ -1196,18 +1082,6 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -1384,6 +1258,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "license": "MIT", + "optional": true, "engines": { "node": ">=0.8.19" } @@ -1467,12 +1342,6 @@ "license": "MIT", "optional": true }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1505,39 +1374,11 @@ "license": "MIT", "optional": true }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/kruptein": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-2.2.3.tgz", - "integrity": "sha512-BTwprBPTzkFT9oTugxKd3WnWrX630MqUDsnmBuoa98eQs12oD4n4TeI0GbpdGcYn/73Xueg2rfnw+oK4dovnJg==", - "license": "MIT", - "dependencies": { - "asn1.js": "^5.4.1" - }, - "engines": { - "node": ">6" - } - }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" }, "node_modules/make-dir": { "version": "3.1.0", @@ -1591,6 +1432,19 @@ "node": ">= 10" } }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mariadb": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.0.tgz", @@ -1619,16 +1473,10 @@ "node": ">=0.10.0" } }, - "node_modules/mariadb/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, "node_modules/math-intrinsics": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", - "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1706,12 +1554,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "license": "ISC" - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1853,9 +1695,9 @@ "license": "MIT" }, "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "license": "MIT" }, "node_modules/negotiator": { @@ -1868,9 +1710,9 @@ } }, "node_modules/node-abi": { - "version": "3.71.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz", - "integrity": "sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==", + "version": "3.74.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", + "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -1880,9 +1722,9 @@ } }, "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", "license": "MIT" }, "node_modules/node-fetch": { @@ -1930,22 +1772,43 @@ "node": ">= 10.12.0" } }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "node_modules/node-gyp/node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", "license": "ISC", + "optional": true, "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=6" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/npmlog": { + "node_modules/node-gyp/node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/npmlog": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", @@ -1962,6 +1825,34 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1972,9 +1863,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -2054,9 +1945,9 @@ "license": "MIT" }, "node_modules/prebuild-install": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", - "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", "license": "MIT", "dependencies": { "detect-libc": "^2.0.0", @@ -2064,7 +1955,7 @@ "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", + "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", @@ -2205,6 +2096,7 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "license": "MIT", + "optional": true, "engines": { "node": ">= 4" } @@ -2252,9 +2144,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2317,46 +2209,12 @@ "node": ">= 0.8.0" } }, - "node_modules/session-file-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/session-file-store/-/session-file-store-1.5.0.tgz", - "integrity": "sha512-60IZaJNzyu2tIeHutkYE8RiXVx3KRvacOxfLr2Mj92SIsRIroDsH0IlUUR6fJAjoTW4RQISbaOApa2IZpIwFdQ==", - "license": "Apache-2.0", - "dependencies": { - "bagpipe": "^0.3.5", - "fs-extra": "^8.0.1", - "kruptein": "^2.0.4", - "object-assign": "^4.1.1", - "retry": "^0.12.0", - "write-file-atomic": "3.0.3" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "license": "ISC" }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -2498,9 +2356,9 @@ } }, "node_modules/socks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", "license": "MIT", "optional": true, "dependencies": { @@ -2559,12 +2417,6 @@ "license": "BSD-3-Clause", "optional": true }, - "node_modules/sqlite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/sqlite/-/sqlite-5.1.1.tgz", - "integrity": "sha512-oBkezXa2hnkfuJwUo44Hl9hS3er+YFtueifoajrgidvqsJRQFpc5fKoAkAor1O5ZnLoa28GBScfHXs8j0K358Q==", - "license": "MIT" - }, "node_modules/sqlite3": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", @@ -2589,6 +2441,12 @@ } } }, + "node_modules/sqlite3/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, "node_modules/ssri": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", @@ -2685,9 +2543,9 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", + "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", "license": "MIT", "dependencies": { "chownr": "^1.1.1", @@ -2767,15 +2625,6 @@ "node": ">= 0.6" } }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -2814,15 +2663,6 @@ "imurmurhash": "^0.1.4" } }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2903,18 +2743,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 626416a..c42b02d 100644 --- a/package.json +++ b/package.json @@ -5,20 +5,22 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, + "repository": { + "type": "git", + "url": "https://git.chrischro.me/AstroCom/AstroCom-API" + }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { "bcrypt": "^5.1.1", - "connect-sqlite": "^0.0.1", "dotenv": "^16.4.7", "ejs": "^3.1.10", "escape-html": "^1.0.3", "express": "^4.21.2", "express-session": "^1.18.1", "mariadb": "^3.4.0", - "session-file-store": "^1.5.0", "sqlite3": "^5.1.7" } } diff --git a/routes/admin.js b/routes/admin.js new file mode 100644 index 0000000..2f6a8c6 --- /dev/null +++ b/routes/admin.js @@ -0,0 +1,89 @@ +const pool = global.db_pool; +if (!pool) { + throw new Error('Database pool is not defined'); +} + +const express = require('express'); +const app = new express.Router(); + +app.get('/logout', (req, res) => { + req.session.destroy(); + res.redirect('/admin/login'); +}); + +app.get('/login', (req, res) => { + res.render('admin/login'); +}); + +app.get('/', (req, res) => { + if (!req.session.adminAuthenticated) { + res.redirect('/admin/login'); + return; + } + res.render('admin/index', { user: req.session.user }); +}); + +app.get('/create', (req, res) => { + if (!req.session.adminAuthenticated) { + res.redirect('/admin/login'); + return; + } + res.render('admin/create', { user: req.session.user }); +}); + +app.get('/route/:id', (req, res) => { + if (!req.session.adminAuthenticated) { + res.redirect('/admin/login'); + return; + } + pool.getConnection().then(conn => { + conn.query('SELECT * FROM routes WHERE id = ?', [req.params.id]).then((rows) => { + const row = rows[0]; + if (!row) { + res.status(404).send('Not Found'); + return; + } + res.render('admin/edit', { user: req.session.user, data: row }); + }).catch(err => { + console.error('Error getting route:', err); + res.status(500).send('Internal server error'); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.post('/login', (req, res) => { + const username = req.body.username; + const password = req.body.password; + pool.getConnection().then(conn => { + conn.query("SELECT * FROM users WHERE username = ?", [String(username)]).then((rows) => { + const row = rows[0]; + if (!row) { + res.status(401).send('Unauthorized (Not Found)'); + return; + } + bcrypt.compare(password, row.passwordHash, (err, result) => { + if (err) { + console.error('Error comparing password:', err); + res.status(500).send('Internal server error'); + return; + } + if (result) { + req.session.adminAuthenticated = true; + req.session.user = row.username; + res.redirect('/admin'); + } else { + res.status(401).send('Unauthorized'); + } + }); + }).catch(err => { + console.error('Error getting user:', err); + res.status(500).send('Internal server error'); + }).finally(() => { + conn.release(); + }); + }); +}); + +module.exports = app; \ No newline at end of file diff --git a/routes/api.js b/routes/api.js new file mode 100644 index 0000000..f37797a --- /dev/null +++ b/routes/api.js @@ -0,0 +1,516 @@ +const pool = global.db_pool; +if (!pool) { + throw new Error('Database pool is not defined'); +} + +const express = require('express'); +const app = new express.Router(); + +const analytics = require('../analytics'); + +// Admin routes (Authenticated) + +app.get('/v1/admin/routes', (req, res) => { // Get all routes + if (!req.session.adminAuthenticated) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + pool.getConnection().then(conn => { + conn.query('SELECT * FROM routes').then((rows) => { + res.json(rows); + }).catch(err => { + console.error('Error getting routes:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.get('/v1/admin/route/:id', (req, res) => { // Get route + if (!req.session.adminAuthenticated) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + pool.getConnection().then(conn => { + conn.query('SELECT * FROM routes WHERE id = ?', [req.params.id]).then((rows) => { + const row = rows[0]; + if (!row) { + res.status(404).json({ error: 'Not Found' }); + return; + } + res.json(row); + }).catch(err => { + console.error('Error getting route:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.post('/v1/admin/route', (req, res) => { // Create a new route + if (!req.session.adminAuthenticated) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + const server = req.body.server; + const port = req.body.port; + const auth = req.body.auth || "from-astrocom"; + const secret = req.body.secret || crypto.randomBytes(15).toString('hex'); + const block_start = req.body.block_start; + const block_length = req.body.block_length || 9999; + const apiKey = crypto.randomBytes(32).toString('hex'); + const contact = req.body.contact || "Unknown"; + // Validate all inputs exist + if (!server || !port || !block_start) { + res.status(400).json({ error: 'Bad Request' }); + return; + } + // Check if route already exists (OR conditions on server, and block range) + pool.getConnection().then(conn => { + conn.query('SELECT * FROM routes WHERE block_start <= ? AND block_start + block_length >= ?', [block_start, block_start]).then((rows) => { + const row = rows[0]; + if (row) { + res.status(409).json({ error: 'Conflict' }); + return; + } else { + conn.query('INSERT INTO routes (server, port, auth, secret, block_start, block_length, apiKey, contact) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', + [server, port, auth, secret, block_start, block_length, apiKey, contact]).then(() => { + res.status(201).json({ message: 'Created' }); + }).catch(err => { + console.error('Error creating route:', err); + res.status(500).json({ error: 'Internal server error' }); + }); + } + }).catch(err => { + console.error('Error checking for existing route:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.put('/v1/admin/route/:id', (req, res) => { // Update a route + // Check if authenticated + if (!req.session.adminAuthenticated) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + // Check if route exists + pool.getConnection().then(conn => { + conn.query('SELECT * FROM routes WHERE id = ?', [req.params.id]).then((rows) => { + const row = rows[0]; + if (!row) { + res.status(404).json({ error: 'Not Found' }); + return; + } + // Update route + const server = req.body.server || row.server; + const port = req.body.port || row.port; + const auth = req.body.auth || row.auth; + const secret = req.body.secret || row.secret; + const block_start = req.body.block_start || row.block_start; + const block_length = req.body.block_length || row.block_length; + const contact = req.body.contact || row.contact; + console.log(`Updating ${req.params.id} to ${server}:${port} with ${auth}:${secret} for ${block_start} - ${block_start + block_length}. Contact: ${contact}`); + conn.query('UPDATE routes SET server = ?, port = ?, auth = ?, secret = ?, block_start = ?, block_length = ?, contact = ? WHERE id = ?', + [server, port, auth, secret, block_start, block_length, contact, req.params.id]).then(() => { + res.json({ message: 'Updated' }); + }).catch(err => { + console.error('Error updating route:', err); + res.status(500).json({ error: 'Internal server error' }); + }); + }).catch(err => { + console.error('Error getting route:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.delete('/v1/admin/route/:id', (req, res) => { // Delete a route + if (!req.session.adminAuthenticated) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + pool.getConnection().then(async conn => { + await conn.query('DELETE FROM directory WHERE route = ?', [req.params.id]) + conn.query('DELETE FROM routes WHERE id = ?', [req.params.id]).then(() => { + res.json({ message: 'Deleted' }); + }).catch(err => { + console.error('Error deleting route:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.delete('/v1/admin/directory/:number', (req, res) => { // Delete a directory entry + if (!req.session.adminAuthenticated) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + const number = Number(req.params.number); + if (!number) { + res.status(400).json({ error: 'Bad Request' }); + return; + } + pool.getConnection().then(conn => { + conn.query('DELETE FROM directory WHERE number = ?', [number]).then(() => { + res.status(200).json({ message: 'Deleted' }); + }).catch(err => { + console.error('Error deleting directory entry:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.get("/v1/admin/callLogs", (req, res) => { + if (!req.session.adminAuthenticated) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + // if ?page is set, return 100 results from that page, if no page assume page 1 + const page = Number(req.query.page) || 1; + const offset = (page - 1) * 100; + // Get full count of call logs to calculate total pages + pool.getConnection().then(conn => { + conn.query("SELECT COUNT(*) as count FROM callLogs").then((rows) => { + const totalPages = Math.ceil(rows[0].count / 100); + conn.query("SELECT * FROM callLogs ORDER BY timestamp DESC LIMIT 100 OFFSET ?", [offset]).then((rows) => { + res.json({ totalPages, page, data: rows }); + }).catch(err => { + console.error('Error getting call logs:', err); + res.status(500).json({ error: 'Internal server error' }); + }); + }).catch(err => { + console.error('Error getting call log count:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); + }); +}); + +// User routes (Authenticated) + +app.get('/v1/user/route', (req, res) => { // Get route + if (!req.session.userAuthenticated) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + res.json(req.session.userData); +}); + +app.put('/v1/user/route', (req, res) => { // Update route + if (!req.session.userAuthenticated) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + if (!req.session.userData.apiKey) { + req.session.destroy(); // Something weird happened, destroy session + res.status(401).json({ error: 'Unauthorized' }); + return; + } + // Does not allow for ID to be specified, always update current users route + const server = req.body.server || req.session.userData.server; + const port = req.body.port || req.session.userData.port; + const auth = req.body.auth || req.session.userData.auth; + const secret = req.body.secret || req.session.userData.secret; + pool.getConnection().then(conn => { + conn.query('UPDATE routes SET server = ?, port = ?, auth = ?, secret = ? WHERE apiKey = ?', + [server, port, auth, secret, req.session.userData.apiKey]).then(() => { + req.session.userData.server = server; + req.session.userData.port = port; + req.session.userData.auth = auth; + req.session.userData.secret = secret; + res.json({ message: 'Updated' }); + }).catch(err => { + console.error('Error updating route:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.get('/v1/user/directory', (req, res) => { // Get directory entries created by user + if (!req.session.userAuthenticated) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + pool.getConnection().then(conn => { + conn.query('SELECT * FROM directory WHERE route = ?', [req.session.userData.id]).then((rows) => { + res.json(rows); + }).catch(err => { + console.error('Error getting routes:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.post('/v1/user/directory', (req, res) => { // Create a new directory entry + // Check if authenticated + if (!req.session.userAuthenticated) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + // Check that the number is within the block range for the current user + 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 < req.session.userData.block_start || number > req.session.userData.block_start + req.session.userData.block_length) { + res.status(403).json({ error: 'Forbidden' }); + return; + } + + // Remove html + name = require("escape-html")(name); + + const route = req.session.userData.id; + // If number already exists, update, otherwise insert + pool.getConnection().then(conn => { + conn.query('SELECT * FROM directory WHERE number = ? AND route = ?', [number, route]).then((rows) => { + const row = rows[0]; + if (row) { + conn.query('UPDATE directory SET name = ? WHERE number = ? AND route = ?', + [name, number, route]).then(() => { + res.json({ message: 'Updated' }); + }).catch(err => { + console.error('Error updating directory entry:', err); + res.status(500).json({ error: 'Internal server error' }); + }); + } else { + conn.query('INSERT INTO directory (number, name, route) VALUES (?, ?, ?)', + [number, name, route]).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' }); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.delete('/v1/user/directory/:number', (req, res) => { // Delete a directory entry + if (!req.session.userAuthenticated) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + const number = Number(req.params.number); + if (!number) { + res.status(400).json({ error: 'Bad Request' }); + return; + } + pool.getConnection().then(conn => { + conn.query('DELETE FROM directory WHERE number = ? AND route = ?', [number, req.session.userData.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' }); + }).finally(() => { + conn.release(); + }); + }); +}); + +// Public routes + +app.get("/v1/directory", (req, res) => { + pool.getConnection().then(conn => { + conn.query("SELECT * FROM directory").then((rows) => { + res.json(rows); + }).catch(err => { + console.error('Error getting directory:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.get("/analytics", (req, res) => { + pool.getConnection().then(conn => { + conn.query("SELECT * FROM analytics").then((total) => { + conn.query("SELECT * FROM dailyAnalytics").then((daily) => { + // Find the latest date and add "current:true" to it + var latest = { tag_date: "1970-01-01", count: 0 }; + daily.forEach((entry) => { + if (entry.tag_date > latest.tag_date) { + latest = entry; + } + }); + latest.current = true; + res.json({ total, daily }); + }).catch(err => { + console.error('Error getting daily analytics:', err); + res.status(500).send('Internal server error'); + }); + }).catch(err => { + console.error('Error getting analytics:', err); + res.status(500).send('Internal server error'); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.get("/v1/checkAvailability/:number", (req, res) => { + // Check if the number is 7 digits + const number = Number(req.params.number); + if (number < 2000000 || number > 9999999) { + res.status(400).json({ error: `Number is outside valid range` }); + return; + } + pool.getConnection().then(conn => { + conn.query('SELECT * FROM routes WHERE block_start <= ? AND block_start + block_length >= ?', [number, number]).then((rows) => { + const row = rows[0]; + if (row) { + res.json({ available: false, block: row.block_start }); + } else { + res.json({ available: true }); + } + }).catch(err => { + console.error('Error getting route:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.get("/healthcheck", (req, res) => { + // Check ability to connect to database with select * from routes + pool.getConnection().then(conn => { + conn.query('SELECT * FROM routes').then(() => { + res.status(200).send('OK'); + }).catch(err => { + console.error('Error checking health:', err); + res.status(500).send('Internal server error'); + }).finally(() => { + conn.release(); + }); + }); +}); + +// AstroCom Asterisk routes (Authenticated) + +app.get('/v1/route/:apiKey/:ani/:number', (req, res) => { + const apiKey = req.params.apiKey; + const number = Number(req.params.number); + const ani = Number(req.params.ani); + pool.getConnection().then(conn => { + //conn.query("SELECT * FROM routes WHERE apiKey = ? AND block_start <= ? AND block_start + block_length >= ?", [apiKey, ani, ani]).then((rows) => { + conn.query("SELECT * FROM routes WHERE apiKey = ?", [apiKey]).then((rows) => { // We'll try this Nick, if it doesn't work we'll go back to the original + const row = rows[0]; + // If no row or error, return 401 + if (!row) { + res.status(401).send(`${process.env.MSG_ROUTE_ADDRESS}/401`) + return; + } + conn.query('SELECT * FROM routes WHERE block_start <= ? AND block_start + block_length >= ?', [number, number]).then((rows) => { + const row = rows[0]; + if (row) { + // Check if the ANI is within the block range + // If it is, return `local` + console.log(`New Call: ${ani} -> ${number}`); + analytics.logCall(ani, number); + // incriment estCallsMade analytics + analytics.addAnalytic("estCallsMade"); + analytics.dailyAnalytic("dailyCallsMade"); + if (ani >= row.block_start && ani <= row.block_start + row.block_length) { + res.status(200).send('local'); + } else { + res.status(200).send(`IAX2/${row.auth}:${row.secret}@${row.server}:${row.port}/${number}`); + } + } else { + res.status(404).send(`${process.env.MSG_ROUTE_ADDRESS}/404`); + } + }).catch(err => { + console.error('Error getting route:', err); + res.status(500).send(`${process.env.MSG_ROUTE_ADDRESS}/500`) + }); + }).catch(err => { + console.error(err); + res.status(401).send(`${process.env.MSG_ROUTE_ADDRESS}/401`) + }).finally(() => { + conn.release(); + }); + }); +}); + +app.get('/v1', (req, res) => { // Backwards compatibility with TandmX cause why not, it's easy + const apiKey = req.query.auth; + const number = Number(req.query.number); + const ani = Number(req.query.ani); + pool.getConnection().then(conn => { + conn.query("SELECT * FROM routes WHERE apiKey = ? AND block_start <= ? AND block_start + block_length >= ?", [apiKey, ani, ani]).then((rows) => { + const row = rows[0]; + // If no row or error, return 401 + if (!row) { + res.status(401).send(`${process.env.MSG_ROUTE_ADDRESS}/401`) + return; + } + conn.query('SELECT * FROM routes WHERE block_start <= ? AND block_start + block_length >= ?', [number, number]).then((rows) => { + const row = rows[0]; + if (row) { + // Check if the ANI is within the block range + // If it is, return `local` + console.log(`New Call: ${ani} -> ${number}`); + analytics.logCall(ani, number); + analytics.addAnalytic("estCallsMade"); + analytics.dailyAnalytic("dailyCallsMade"); + if (ani >= row.block_start && ani <= row.block_start + row.block_length) { + res.status(200).send('local'); + } else { + res.status(200).send(`IAX2/${row.auth}:${row.secret}@${row.server}:${row.port}/${number}`); + } + } else { + res.status(404).send(`${process.env.MSG_ROUTE_ADDRESS}/404`); + } + }).catch(err => { + console.error('Error getting route:', err); + res.status(500).send(`${process.env.MSG_ROUTE_ADDRESS}/500`) + }); + }).catch(err => { + console.error(err); + res.status(401).send(`${process.env.MSG_ROUTE_ADDRESS}/401`) + }).finally(() => { + conn.release(); + }); + }); +}); + +// Management Routes (Like restarting the server) + +app.post("/api/v1/manage/restart", (req, res) => { + // Check Authorization header against process.env.MANAGEMENT_AUTH + if (req.headers.authorization !== (process.env.MANAGEMENT_AUTH || crypto.randomBytes(32).toString('hex'))) { // Default to random in case we forget to configure it + res.status(401).send("Unauthorized"); + return; + } + res.status(200).send("Restarting server..."); + setTimeout(() => { + process.exit(0); + }, 500); +}); + +module.exports = app; \ No newline at end of file diff --git a/routes/user.js b/routes/user.js new file mode 100644 index 0000000..f267472 --- /dev/null +++ b/routes/user.js @@ -0,0 +1,58 @@ +const pool = global +const express = require('express'); +const app = new express.Router(); + +app.get('/user', (req, res) => { + if (!req.session.userAuthenticated) { + res.redirect('/user/login'); + return; + } + res.render('user/index', { user: req.session.user }); +}); + +app.get('/user/login', (req, res) => { + res.render('user/login'); +}); + +app.post('/user/login', (req, res) => { + const apiKey = req.body.apiKey; + pool.getConnection().then(conn => { + conn.query("SELECT * FROM routes WHERE apiKey = ?", [apiKey]).then((rows) => { + const row = rows[0]; + if (!row) { + res.status(401).send('Unauthorized'); + return; + } + req.session.userAuthenticated = true; + req.session.userData = row; + res.redirect('/user'); + }).catch(err => { + console.error('Error getting route:', err); + res.status(500).send('Internal server error'); + }).finally(() => { + conn.release(); + }); + }); +}); + +app.get('/user/logout', (req, res) => { + req.session.destroy(); + res.redirect('/user/login'); +}); + +app.get("/user/edit", (req, res) => { + if (!req.session.userAuthenticated) { + res.redirect('/user/login'); + return; + } + // Remove block_start, block_length, and apiKey from the response + responseData = { + server: req.session.userData.server, + port: req.session.userData.port, + auth: req.session.userData.auth, + secret: req.session.userData.secret + } + res.render('user/edit', { data: responseData }); +}); + +module.exports = app; \ No newline at end of file