diff --git a/index.js b/index.js index daa587e..37ccd73 100644 --- a/index.js +++ b/index.js @@ -17,21 +17,42 @@ const { execSync } = require('child_process'); const express = require('express'); const expressSession = require('express-session'); const ejs = require("ejs") -const sqlite3 = require('sqlite3').verbose(); +const mariadb = require('mariadb'); const bcrypt = require("bcrypt") const crypto = require("crypto") const app = express(); const port = process.env.SERVER_PORT || 3000; -const db = new sqlite3.Database('astrocom.db', (err) => { - if (err) { - console.error('Error connecting to database:', err); - } else { - console.log('Connected to SQLite database'); - } +const pool = mariadb.createPool({ + host: process.env.DB_HOST || '127.0.0.1', + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || 'astrocom', + connectionLimit: 10 +}); +const saltRounds = 10; + +pool.getConnection().then((conn) => { + require("./migrations")(pool).then(() => { + conn.query("SELECT * FROM users WHERE id = 1").then((row) => { + if (!row || process.env.RESET_ADMIN == "true") { + // delete all users (The big scary one lol) + conn.query("DELETE FROM users").then(() => { + // Generate 32 char random string + const passwd = crypto.randomBytes(32).toString('hex'); + bcrypt.hash(passwd, 10).then((hash) => { + conn.query("INSERT INTO users (id, username, passwordHash) VALUES (1, 'admin', ?)", + [hash]).then(() => { + console.log(`Created admin user with password: ${passwd}`); + }); + }); + }); + } + }); + }).finally(() => { + conn.release(); + }); }); -// Run migrations -require("./migrations")(db) const gitCommitHashShort = execSync('git rev-parse --short HEAD').toString().trim(); const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim(); @@ -39,47 +60,9 @@ const version = `${gitCommitHashShort}-${branch}`; console.log(`Version: ${version}`); - -// Check if user 1 exists, if not, create it -const saltRounds = 10; - -db.get("SELECT * FROM users WHERE id = 1", [], (err, row) => { - if (err) { - console.error('Error checking for admin user:', err); - return; - } - if (!row || process.env.RESET_ADMIN == "true") { - // delete all users (The big scary one lol) - db.run("DELETE FROM users", [], (err) => { - if (err) { - console.error('Error deleting users:', err); - return; - } - }); - // Generate 32 char random string - const passwd = crypto.randomBytes(32).toString('hex'); - bcrypt.hash(passwd, saltRounds, (err, hash) => { - if (err) { - console.error('Error creating hash:', err); - return; - } - db.run("INSERT INTO users (id, username, passwordHash) VALUES (1, 'admin', ?)", - [hash], - (err) => { - if (err) { - console.error('Error creating admin user:', err); - } else { - console.log(`Created admin user with password: ${passwd}`); - } - }); - }); - } -}); - app.use(express.json()); app.use(express.urlencoded({ extended: true })); - app.use(expressSession({ store: expressSession.MemoryStore(), secret: process.env.SESSION_SECRET || 'default_secret', @@ -97,19 +80,25 @@ app.set('views', __dirname + '/views'); app.use(express.static('public')); const addAnalytic = (tag) => { - db.get("SELECT * FROM analytics WHERE tag = ?", [tag], (err, row) => { - if (err) { + pool.getConnection().then(conn => { + conn.query("SELECT * FROM analytics WHERE tag = ?", [tag]).then((rows) => { + console.log(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); - } - if (!row) { - db.run("INSERT INTO analytics (tag, count) VALUES (?, 1)", [tag], (err) => { - if (err) console.error('Error creating analytics:', err); - }); - } else { - db.run("UPDATE analytics SET count = count + 1 WHERE tag = ?", [tag], (err) => { - if (err) console.error('Error updating analytics:', err); - }); - } + }).finally(() => { + conn.release(); + }); + }).catch(err => { + console.error('Error getting connection:', err); }); } @@ -119,19 +108,24 @@ const dailyAnalytic = (tag) => { // This is a bit more complex, but it's just a const date = new Date(); const today = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`; - db.get("SELECT * FROM dailyAnalytics WHERE tag = ? AND tag_date = ?", [tag, today], (err, row) => { - if (err) { + 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); - } - if (!row) { - db.run("INSERT INTO dailyAnalytics (tag, tag_date, count) VALUES (?, ?, 1)", [tag, today], (err) => { - if (err) console.error('Error creating daily analytics:', err); - }); - } else { - db.run("UPDATE dailyAnalytics SET count = count + 1 WHERE tag = ? AND tag_date = ?", [tag, today], (err) => { - if (err) console.error('Error updating daily analytics:', err); - }); - } + }).finally(() => { + conn.release(); + }); + }).catch(err => { + console.error('Error getting connection:', err); }); } @@ -175,46 +169,52 @@ app.get('/admin/route/:id', (req, res) => { res.redirect('/admin/login'); return; } - db.get('SELECT * FROM routes WHERE id = ?', [req.params.id], (err, row) => { - if (err) { + 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'); - return; - } - if (!row) { - res.status(404).send('Not Found'); - return; - } - res.render('admin/edit', { user: req.session.user, data: row }); + }).finally(() => { + conn.release(); + }); }); }); app.post('/admin/login', (req, res) => { const username = req.body.username; const password = req.body.password; - db.get("SELECT * FROM users WHERE username = ?", [String(username)], (err, row) => { - if (err) { - console.error('Error getting user:', err); - res.status(500).send('Internal server error'); - return; - } - 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'); + 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; } - if (result) { - req.session.adminAuthenticated = true; - req.session.user = row.username; - res.redirect('/admin'); - } else { - res.status(401).send('Unauthorized'); - } + 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(); }); }); }) @@ -224,13 +224,15 @@ app.get('/api/v1/admin/routes', (req, res) => { // Get all routes res.status(401).json({ error: 'Unauthorized' }); return; } - db.all('SELECT * FROM routes', (err, rows) => { - if (err) { + 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' }); - return; - } - res.json(rows); + }).finally(() => { + conn.release(); + }); }); }); @@ -239,17 +241,20 @@ app.get('/api/v1/admin/route/:id', (req, res) => { // Get route res.status(401).json({ error: 'Unauthorized' }); return; } - db.get('SELECT * FROM routes WHERE id = ?', [req.params.id], (err, row) => { - if (err) { + 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' }); - return; - } - if (!row) { - res.status(404).json({ error: 'Not Found' }); - return; - } - res.json(row); + }).finally(() => { + conn.release(); + }); }); }); @@ -272,27 +277,27 @@ app.post('/api/v1/admin/route', (req, res) => { // Create a new route return; } // Check if route already exists (OR conditions on server, and block range) - db.get('SELECT * FROM routes WHERE block_start <= ? AND block_start + block_length >= ?', [block_start, block_start], (err, row) => { - if (err) { - console.error('Error checking for existing route:', err); - res.status(500).json({ error: 'Internal server error' }); - return; - } - if (row) { - res.status(409).json({ error: 'Conflict' }); - return; - } else { - db.run('INSERT INTO routes (server, port, auth, secret, block_start, block_length, apiKey, contact) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', - [server, port, auth, secret, block_start, block_length, apiKey, contact], - (err) => { - if (err) { + 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' }); - return; - } - res.status(201).json({ message: 'Created' }); - }); - } + }); + } + }).catch(err => { + console.error('Error checking for existing route:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); }); }); @@ -303,35 +308,35 @@ app.put('/api/v1/admin/route/:id', (req, res) => { // Update a route return; } // Check if route exists - db.get('SELECT * FROM routes WHERE id = ?', [req.params.id], (err, row) => { - if (err) { - console.error('Error getting route:', err); - res.status(500).json({ error: 'Internal server error' }); - return; - } - 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}`); - db.run('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], - (err) => { - if (err) { + 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' }); - return; - } - res.json({ message: 'Updated' }); - }); + }); + }).catch(err => { + console.error('Error getting route:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); }); }); @@ -340,13 +345,15 @@ app.delete('/api/v1/admin/route/:id', (req, res) => { // Delete a route res.status(401).json({ error: 'Unauthorized' }); return; } - db.run('DELETE FROM routes WHERE id = ?', [req.params.id], (err) => { - if (err) { + pool.getConnection().then(conn => { + 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' }); - return; - } - res.json({ message: 'Deleted' }); + }).finally(() => { + conn.release(); + }); }); }); @@ -360,13 +367,15 @@ app.delete('/api/v1/admin/directory/:number', (req, res) => { // Delete a direct res.status(400).json({ error: 'Bad Request' }); return; } - db.run('DELETE FROM directory WHERE number = ?', [number], (err) => { - if (err) { + 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' }); - return; - } - res.status(200).json({ message: 'Deleted' }); + }).finally(() => { + conn.release(); + }); }); }); @@ -379,20 +388,20 @@ app.get("/api/v1/admin/callLogs", (req, res) => { const page = Number(req.query.page) || 1; const offset = (page - 1) * 100; // Get full count of call logs to calculate total pages - db.get("SELECT COUNT(*) as count FROM callLogs", [], (err, row) => { - if (err) { - console.error('Error getting call log count:', err); - res.status(500).json({ error: 'Internal server error' }); - return; - } - const totalPages = Math.ceil(row.count / 100); - db.all("SELECT * FROM callLogs ORDER BY timestamp DESC LIMIT 100 OFFSET ?", [offset], (err, rows) => { - if (err) { + 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' }); - return; - } - res.json({ totalPages, page, data: rows }); + }); + }).catch(err => { + console.error('Error getting call log count:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); }); }); }); @@ -414,19 +423,22 @@ app.get('/user/login', (req, res) => { app.post('/user/login', (req, res) => { const apiKey = req.body.apiKey; - db.get("SELECT * FROM routes WHERE apiKey = ?", [apiKey], (err, row) => { - if (err) { + 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'); - return; - } - if (!row) { - res.status(401).send('Unauthorized'); - return; - } - req.session.userAuthenticated = true; - req.session.userData = row; - res.redirect('/user'); + }).finally(() => { + conn.release(); + }); }); }); @@ -447,7 +459,7 @@ app.get("/user/edit", (req, res) => { auth: req.session.userData.auth, secret: req.session.userData.secret } - res.render('user/edit', {data: responseData}); + res.render('user/edit', { data: responseData }); }); app.get('/api/v1/user/route', (req, res) => { // Get route @@ -473,20 +485,21 @@ app.put('/api/v1/user/route', (req, res) => { // Update route 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; - db.run('UPDATE routes SET server = ?, port = ?, auth = ?, secret = ? WHERE apiKey = ?', - [server, port, auth, secret, req.session.userData.apiKey], - (err) => { - if (err) { + 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' }); - return; - } - req.session.userData.server = server; - req.session.userData.port = port; - req.session.userData.auth = auth; - req.session.userData.secret = secret; - res.json({ message: 'Updated' }); - }); + }).finally(() => { + conn.release(); + }); + }); }); app.get('/api/v1/user/directory', (req, res) => { // Get directory entries created by user @@ -494,13 +507,15 @@ app.get('/api/v1/user/directory', (req, res) => { // Get directory entries creat res.status(401).json({ error: 'Unauthorized' }); return; } - db.all('SELECT * FROM directory WHERE route = ?', [req.session.userData.id], (err, rows) => { - if (err) { + 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' }); - return; - } - res.json(rows); + }).finally(() => { + conn.release(); + }); }); }); @@ -528,35 +543,32 @@ app.post('/api/v1/user/directory', (req, res) => { // Create a new directory ent const route = req.session.userData.id; // If number already exists, update, otherwise insert - db.get('SELECT * FROM directory WHERE number = ? AND route = ?', [number, route], (err, row) => { - if (err) { - console.error('Error checking for existing directory entry:', err); - res.status(500).json({ error: 'Internal server error' }); - return; - } - if (row) { - db.run('UPDATE directory SET name = ? WHERE number = ? AND route = ?', - [name, number, route], - (err) => { - if (err) { + 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' }); - return; - } - res.json({ message: 'Updated' }); - }); - } else { - db.run('INSERT INTO directory (number, name, route) VALUES (?, ?, ?)', - [number, name, route], - (err) => { - if (err) { + }); + } 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' }); - return; - } - res.status(201).json({ message: 'Created' }); - }); - } + }); + } + }).catch(err => { + console.error('Error checking for existing directory entry:', err); + res.status(500).json({ error: 'Internal server error' }); + }).finally(() => { + conn.release(); + }); }); }); @@ -570,13 +582,15 @@ app.delete('/api/v1/user/directory/:number', (req, res) => { // Delete a directo res.status(400).json({ error: 'Bad Request' }); return; } - db.run('DELETE FROM directory WHERE number = ? AND route = ?', [number, req.session.userData.id], (err) => { - if (err) { + 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' }); - return; - } - res.status(200).json({ message: 'Deleted' }); + }).finally(() => { + conn.release(); + }); }); }); @@ -585,13 +599,15 @@ app.delete('/api/v1/user/directory/:number', (req, res) => { // Delete a directo // == Directory routes == (unauthenticated) app.get("/api/v1/directory", (req, res) => { - db.all("SELECT * FROM directory", (err, rows) => { - if (err) { + 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' }); - return; - } - res.json(rows); + }).finally(() => { + conn.release(); + }); }); }); @@ -618,27 +634,27 @@ app.get("/discord", (req, res) => { }); app.get("/api/analytics", (req, res) => { - db.all("SELECT * FROM analytics", (err, total) => { - if (err) { - console.error('Error getting analytics:', err); - res.status(500).send('Internal server error'); - return; - } - db.all("SELECT * FROM dailyAnalytics", (err, daily) => { - if (err) { + 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'); - return; - } - // 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 analytics:', err); + res.status(500).send('Internal server error'); + }).finally(() => { + conn.release(); }); }); }); @@ -654,39 +670,47 @@ app.get("/api/v1/checkAvailability/:number", (req, res) => { res.status(400).json({ error: `Number is outside valid range` }); return; } - db.get('SELECT * FROM routes WHERE block_start <= ? AND block_start + block_length >= ?', [number, number], (err, row) => { - if (err) { + 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' }); - return; - } - if (row) { - res.json({ available: false, block: row.block_start }); - } else { - res.json({ available: true }); - } + }).finally(() => { + conn.release(); + }); }); }); app.get("/api/healthcheck", (req, res) => { // Check ability to connect to database with select * from routes - db.get('SELECT * FROM routes', [], (err, row) => { - if (err) { + 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'); - return; - } - res.status(200).send('OK'); + }).finally(() => { + conn.release(); + }); }); }); // logCall function (caller, callee) const logCall = (caller, callee) => { - db.run('INSERT INTO callLogs (caller, callee, timestamp) VALUES (?, ?, ?)', - [caller, callee, Math.floor(Date.now())], - (err) => { - if (err) console.error('Error logging call:', err); - }); + 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(); + }); + }); } // Query to get a route @@ -694,33 +718,41 @@ 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); - db.get("SELECT * FROM routes WHERE apiKey = ? AND block_start <= ? AND block_start + block_length >= ?", [apiKey, ani, ani], (err, row) => { - // If no row or error, return 401 - if (err || !row) { - console.error(err); - res.status(401).send(`${process.env.MSG_ROUTE_ADDRESS}/401`) - return; - } - db.get('SELECT * FROM routes WHERE block_start <= ? AND block_start + block_length >= ?', [number, number], (err, row) => { - if (err) { + 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); + // 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`) - } else 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(err); + res.status(401).send(`${process.env.MSG_ROUTE_ADDRESS}/401`) + }).finally(() => { + conn.release(); }); }); }); @@ -729,32 +761,40 @@ app.get('/api/v1', (req, res) => { // Backwards compatibility with TandmX cause const apiKey = req.query.auth; const number = Number(req.query.number); const ani = Number(req.query.ani); - db.get("SELECT * FROM routes WHERE apiKey = ? AND block_start <= ? AND block_start + block_length >= ?", [apiKey, ani, ani], (err, row) => { - // If no row or error, return 401 - if (err || !row) { - console.error(err); - res.status(401).send(`${process.env.MSG_ROUTE_ADDRESS}/401`) - return; - } - db.get('SELECT * FROM routes WHERE block_start <= ? AND block_start + block_length >= ?', [number, number], (err, row) => { - if (err) { + 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`) - } else 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(err); + res.status(401).send(`${process.env.MSG_ROUTE_ADDRESS}/401`) + }).finally(() => { + conn.release(); }); }); }); diff --git a/migrations.js b/migrations.js index 32d1308..19ba8b0 100644 --- a/migrations.js +++ b/migrations.js @@ -1,65 +1,72 @@ -const sqlite3 = require('sqlite3').verbose(); +const mariadb = require('mariadb'); const fs = require('fs'); const path = require('path'); -const util = require("util"); +const util = require("util") -function runMigrations(db) { - return new Promise((resolve, reject) => { - const migrationDir = path.join(__dirname, 'migrations'); - const runQuery = util.promisify(db.run.bind(db)); - const getQuery = util.promisify(db.get.bind(db)); +function runMigrations(pool) { + return new Promise((resolve, reject) => { + let connection; - // Ensure a migrations table exists to track applied migrations - runQuery(`CREATE TABLE IF NOT EXISTS migrations ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - );`) - .then(() => { - // Read all migration files - const files = fs.readdirSync(migrationDir).sort(); // Sort to apply in order + pool.getConnection() + .then(conn => { + connection = conn; - return files.reduce((promise, file) => { - return promise.then(() => { - const migrationName = path.basename(file); + // Ensure a migrations table exists to track applied migrations + return connection.query(`CREATE TABLE IF NOT EXISTS migrations ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + );`); + }) + .then(() => { + // Read all migration files + const migrationDir = path.join(__dirname, 'migrations'); + const files = fs.readdirSync(migrationDir).sort(); // Sort to apply in order - // Check if the migration has already been applied - return getQuery( - 'SELECT 1 FROM migrations WHERE name = ? LIMIT 1', - [migrationName] - ).then((row) => { - if (row) { - // console.log(`Skipping already applied migration: ${migrationName}`); - return; // Skip this migration - } + return files.reduce((promise, file) => { + return promise.then(() => { + const migrationName = path.basename(file); - // Read and execute the migration SQL - const migrationPath = path.join(migrationDir, file); - const sql = fs.readFileSync(migrationPath, 'utf8'); + // Check if the migration has already been applied + return connection.query( + 'SELECT 1 FROM migrations WHERE name = ? LIMIT 1', + [migrationName] + ).then(([rows]) => { + if (Object.keys(rows || {}).length > 0) { + //console.log(`Skipping already applied migration: ${migrationName}`); + return; // Skip this migration + } - return runQuery(sql).then(() => { - // Record the applied migration - return runQuery( - 'INSERT INTO migrations (name) VALUES (?)', - [migrationName] - ).then(() => { - console.log(`Applied migration: ${migrationName}`); - }); - }); - }); - }); - }, Promise.resolve()); - }) - .then(() => { - console.log('All migrations applied successfully!'); - resolve(); - }) - .catch((err) => { - console.error('Error running migrations:', err); - reject(err); - }) - }); + // Read and execute the migration SQL + const migrationPath = path.join(migrationDir, file); + const sql = fs.readFileSync(migrationPath, 'utf8'); + return connection.query(sql).then(() => { + // Record the applied migration + return connection.query( + 'INSERT INTO migrations (name) VALUES (?)', + [migrationName] + ).then(() => { + console.log(`Applied migration: ${migrationName}`); + }); + }); + }); + }); + }, Promise.resolve()); + }) + .then(() => { + console.log('All migrations applied successfully!'); + resolve(); + }) + .catch(err => { + console.errorr('Error running migrations:', err); + reject(err); + }) + .finally(() => { + if (connection) connection.release(); + }); + }); } -module.exports = runMigrations; + +module.exports = runMigrations \ No newline at end of file diff --git a/migrations/001_gen_routes_table.sql b/migrations/001_gen_routes_table.sql index 7d96a5f..172986d 100644 --- a/migrations/001_gen_routes_table.sql +++ b/migrations/001_gen_routes_table.sql @@ -1,10 +1,10 @@ CREATE TABLE IF NOT EXISTS routes ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - server TEXT NOT NULL, + id INTEGER PRIMARY KEY AUTO_INCREMENT, + server VARCHAR(255) NOT NULL, port INTEGER NOT NULL DEFAULT 4569, - auth TEST NOT NULL DEFAULT 'from-astrocom', - secret TEXT NOT NULL, + auth VARCHAR(255) NOT NULL DEFAULT 'from-astrocom', + secret VARCHAR(255) NOT NULL, block_start INTEGER UNIQUE NOT NULL, block_length INTEGER NOT NULL DEFAULT 9999, - apiKey TEXT NOT NULL -) \ No newline at end of file + apiKey VARCHAR(255) NOT NULL +); \ No newline at end of file diff --git a/migrations/002_gen_users_table.sql b/migrations/002_gen_users_table.sql index a91dfb3..55960ae 100644 --- a/migrations/002_gen_users_table.sql +++ b/migrations/002_gen_users_table.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - username TEXT NOT NULL, - passwordHash TEXT NOT NULL -) \ No newline at end of file + id INTEGER PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(255) NOT NULL, + passwordHash VARCHAR(255) NOT NULL +); \ No newline at end of file diff --git a/migrations/003_gen_directory_table.sql b/migrations/003_gen_directory_table.sql index 24ba762..a84aa8f 100644 --- a/migrations/003_gen_directory_table.sql +++ b/migrations/003_gen_directory_table.sql @@ -1,6 +1,6 @@ CREATE TABLE IF NOT EXISTS directory ( number INTEGER PRIMARY KEY NOT NULL, -- This is the directory phone number - name TEXT NOT NULL, -- This is the text of the entry, set by the user. + name VARCHAR(255) NOT NULL, -- This is the VARCHAR(255) of the entry, set by the user. route INTEGER NOT NULL, -- This is the ID of the route that owns this entry. Foreign key to routes.id FOREIGN KEY(route) REFERENCES routes(id) -) \ No newline at end of file +); \ No newline at end of file diff --git a/migrations/004_gen_analytics_table.sql b/migrations/004_gen_analytics_table.sql index 2fdc65c..3f64b1b 100644 --- a/migrations/004_gen_analytics_table.sql +++ b/migrations/004_gen_analytics_table.sql @@ -1,4 +1,4 @@ CREATE TABLE IF NOT EXISTS analytics ( - tag TEXT NOT NULL PRIMARY KEY, + tag VARCHAR(255) NOT NULL PRIMARY KEY, count INTEGER NOT NULL DEFAULT 0 ); \ No newline at end of file diff --git a/migrations/005_gen_dailyAnalytics_table.sql b/migrations/005_gen_dailyAnalytics_table.sql index 13f402b..2c34392 100644 --- a/migrations/005_gen_dailyAnalytics_table.sql +++ b/migrations/005_gen_dailyAnalytics_table.sql @@ -1,6 +1,6 @@ CREATE TABLE IF NOT EXISTS dailyAnalytics ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - tag TEXT NOT NULL, + id INTEGER PRIMARY KEY AUTO_INCREMENT, + tag VARCHAR(255) NOT NULL, count INTEGER NOT NULL DEFAULT 0, - tag_date TEXT NOT NULL + tag_date VARCHAR(255) NOT NULL ); \ No newline at end of file diff --git a/migrations/006_gen_callLogs_table.sql b/migrations/006_gen_callLogs_table.sql index 7855c54..037c40f 100644 --- a/migrations/006_gen_callLogs_table.sql +++ b/migrations/006_gen_callLogs_table.sql @@ -1,6 +1,6 @@ CREATE TABLE callLogs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL, - caller TEXT NOT NULL, - callee TEXT NOT NULL + id INTEGER PRIMARY KEY AUTO_INCREMENT, + timestamp VARCHAR(255) NOT NULL, + caller VARCHAR(255) NOT NULL, + callee VARCHAR(255) NOT NULL ); \ No newline at end of file diff --git a/migrations/007_add_contact_col_routes.sql b/migrations/007_add_contact_col_routes.sql index 46db618..6c37796 100644 --- a/migrations/007_add_contact_col_routes.sql +++ b/migrations/007_add_contact_col_routes.sql @@ -1,2 +1 @@ -ALTER TABLE routes -ADD COLUMN contact TEXT; \ No newline at end of file +ALTER TABLE routes ADD COLUMN contact VARCHAR(255); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2e2832f..9aa4c72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "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" } @@ -131,6 +132,21 @@ "node": ">= 6" } }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -680,6 +696,15 @@ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "license": "MIT" }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1566,6 +1591,40 @@ "node": ">= 10" } }, + "node_modules/mariadb": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.0.tgz", + "integrity": "sha512-hdRPcAzs+MTxK5VG1thBW18gGTlw6yWBe9YnLB65GLo7q0fO5DWsgomIevV/pXSaWRmD3qi6ka4oSFRTExRiEQ==", + "license": "LGPL-2.1-or-later", + "dependencies": { + "@types/geojson": "^7946.0.14", + "@types/node": "^22.5.4", + "denque": "^2.1.0", + "iconv-lite": "^0.6.3", + "lru-cache": "^10.3.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/mariadb/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", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "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", @@ -2729,6 +2788,12 @@ "node": ">= 0.8" } }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, "node_modules/unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", diff --git a/package.json b/package.json index e70d031..626416a 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "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" }