const express = require('express'); const mariadb = require('mariadb'); const cors = require('cors'); const app = express(); app.use(express.json()); app.use(cors()); const version = "V1.1"; const dbip = process.env.DBIP || "192.168.2.54"; const dbusername = process.env.DBUSERNAME || "root"; const dbpassword = process.env.DBPASSWORD || "U&P7UH1mhRG@JF2K"; const db = process.env.DB || "LicenseTracker"; // Database details const pool = mariadb.createPool({ host: "192.168.2.54", user: "root", password: "U&P7UH1mhRG@JF2K", database: "LicenseTracker" }); //Initialize pollrate variable let pollrate = "5000"; // 5 Seconds //Api Key const token = "&v94gt8ZHFTTTeuT"; //Allows the clients to check if the server is alive app.get('/api', (req, res) => { console.log("Welcome to the MPE licenses api"); res.sendStatus(200).json({ success: "Welcome to the MPE licenses api" }); }); //Allows the clients to check if the server is alive app.get('/api/healthcheck', (req, res) => { res.sendStatus(200); }); //This will show all of the records for a specific program. It takes a program name as a parameter in the body and returns all records that have that program name app.post('/api/programRecords', async (req, res) => { try { const { programName } = req.body; if (!programName) { return res.status(400).json({ error: "Program name is required" }); } // Get the records for the specified program name const records = await getProgramRecords(programName); res.status(200).json(records); } catch (error) { console.log("[ERROR]: There was an error retrieving the program records. More info: " + error); res.status(500).json({ error: "Internal Server Error" }); } }); //Test to see if a connection to the database is feasible async function testDatabaseConnection() { let connection; try { // Create a connection to the database connection = await pool.getConnection(); // Test the connection console.log('Database connection successful'); } catch (error) { console.error('Database connection failed:', error.message); } finally { // Ensure the connection is closed if (connection) { try { await connection.end(); } catch (endError) { console.error('Error closing the connection:', endError.message); } } } } const getProgramRecords = async (programName) => { let n = (pollRate / 1000) + ((pollRate / 1000) * 0.25); // Milliseconds to seconds let records = []; //console.log('Retrieving program records'); try { const conn = await pool.getConnection(); try { // Get the records for the specified program name within the time constraints const query = ` SELECT * FROM usedLicenses WHERE program = ? AND date >= DATE_SUB(NOW(), INTERVAL ? SECOND) `; records = await conn.query(query, [programName, n]); } catch (error) { console.log("[ERROR]: There was an error reading from the database. More info: " + error); } finally { conn.release(); // Ensure the connection is released } } catch (error) { console.log("[ERROR]: There was an error reading from the database. More info: " + error); } return records; }; // This gives the client a list of all of the programs that are currently being requested app.post('/api/licenseCount', async (req, res) => { try { const { programName } = req.body; if (!programName) { return res.status(400).json({ error: "Program name is required" }); } // Call getLicensesCount function for the provided programName const count = await getLicensesCount(programName); //console.log(count.toString()); res.status(200).json({ program: programName, count: count.toString() }); } catch (error) { console.log("[ERROR]: There was an error retrieving the license count. More info: " + error); res.status(500).json({ error: "Internal Server Error" }); } }); const getLicensesCount = async (programName) => { let n = (pollRate / 1000) + ((pollRate / 1000) * 0.25); // Milliseconds to seconds let count = 0; try { const conn = await pool.getConnection(); try { // Get the current time minus n seconds const query = ` SELECT COUNT(*) AS count FROM usedLicenses WHERE program = ? AND date >= DATE_SUB(NOW(), INTERVAL ? SECOND) `; const result = await conn.query(query, [programName, n]); count = result[0].count; } catch (error) { console.log("[ERROR]: There was an error reading from the database. More info: " + error); } finally { conn.release(); // Ensure the connection is released } } catch (error) { console.log("[ERROR]: There was an error reading from the database. More info: " + error); } return count; }; // This gives the client a list of all of the programs that are currently being requested app.get('/api/programs', async (req, res) => { try { const programsResult = await getRequestedPrograms(); // Extract program names from the result const programNames = programsResult.map(record => record.name); res.status(200).json(programNames); } catch (error) { console.log("[ERROR]: There was an error retrieving the programs. More info: " + error); res.status(500).json({ error: "Internal Server Error" }); } }); const getRequestedPrograms = async () => { let result = []; try { const conn = await pool.getConnection(); try { // Query to get all requested programs const query = `SELECT name FROM requestedLicenses`; result = await conn.query(query); } catch (error) { console.log("[ERROR]: There was an error retrieving program names. More info: " + error); } finally { conn.release(); // Ensure the connection is released } } catch (error) { console.log("[ERROR]: There was an error retrieving program names. More info: " + error); } return result; }; // This tells the client how often to phone home app.get('/api/pollrate', async (req, res) => { try { pollrate = await getPollRate(); // Used to change the poll rate res.json({ pollrate: `${pollrate}` }); // In milliseconds } catch (error) { console.log("[ERROR]: There was an error retrieving the poll rate. More info: " + error); res.status(500).json({ error: "Internal Server Error" }); } }); const getPollRate = async () => { // Debugging! console.log('Retrieving poll rate'); try { const conn = await pool.getConnection(); try { // Query to get the poll rate where sid = 1 const query = `SELECT pollrate FROM settings WHERE sid = 1`; const result = await conn.query(query); if (result.length > 0) { pollRate = result[0].pollrate; // Extract poll rate from the result } else { console.log("[INFO]: No poll rate found for sid = 1."); } } catch (error) { console.log("[ERROR]: There was an error retrieving the poll rate. More info: " + error); } finally { conn.release(); // Ensure the connection is released } } catch (error) { console.log("[ERROR]: There was an error retrieving the poll rate. More info: " + error); } return pollRate; }; // Retrieves currently used licenses from the database app.get('/api/licenses', async (req, res) => { try { // Retrieve the currently used licenses from the database const licenses = await getLicenses(); res.json(licenses); } catch (error) { console.log("[ERROR]: There was an error retrieving licenses. More info: " + error); res.status(500).json({ error: "Internal Server Error" }); } }); // Retrieves currently used licenses from the database const getLicenses = async () => { let n = (pollRate / 1000) + ((pollRate/1000) * 0.25); //Milliseconds to seconds let records = []; //console.log('Retrieving list'); try { const conn = await pool.getConnection(); try { // Get the current time minus n minutes const query = ` SELECT * FROM usedLicenses WHERE date >= DATE_SUB(NOW(), INTERVAL ? SECOND) `; records = await conn.query(query, [n]); } catch (error) { console.log("[ERROR]: There was an error reading from the database. More info: " + error); } finally { conn.release(); // Ensure the connection is released } } catch (error) { console.log("[ERROR]: There was an error reading from the database. More info: " + error); } return records; }; app.get('/api/count', async (req, res) => { try { // Retrieve the currently used licenses count along with the total and available licenses const programs = await getLicenseCounts(); res.json({ programs }); } catch (error) { console.log("[ERROR]: There was an error retrieving licenses. More info: " + error); res.status(500).json({ error: "Internal Server Error" }); } }); // Retrieves currently used licenses count, total licenses, and available licenses grouped by program const getLicenseCounts = async () => { let n = (pollRate / 1000) + ((pollRate / 1000) * 0.25); // Convert pollRate to seconds and add 25% let records = []; try { const conn = await pool.getConnection(); try { // Get the current time minus n seconds and count the number of licenses per program const query = ` SELECT ul.program, COUNT(*) as activecount, rl.total, (rl.total - COUNT(*)) as available FROM usedLicenses ul JOIN requestedLicenses rl ON ul.program = rl.name WHERE ul.date >= DATE_SUB(NOW(), INTERVAL ? SECOND) GROUP BY ul.program, rl.total `; const results = await conn.query(query, [n]); // Convert the results to handle bigint serialization records = results.map(row => ({ program: row.program, activecount: String(row.activecount), // Convert bigint to string total: String(row.total), // Convert bigint to string available: String(row.available) // Convert bigint to string })); } catch (error) { console.log("[ERROR]: There was an error reading from the database. More info: " + error); } finally { conn.release(); // Ensure the connection is released } } catch (error) { console.log("[ERROR]: There was an error reading from the database. More info: " + error); } return records; }; app.get('/api/zabbix', async (req, res) => { try { const programs = await getZabbixCounts(); res.json(programs); // Directly return the reshaped JSON } catch (error) { console.log("[ERROR]: There was an error retrieving licenses. More info: " + error); res.status(500).json({ error: "Internal Server Error" }); } }); const getZabbixCounts = async () => { let n = (pollRate / 1000) + ((pollRate / 1000) * 0.25); let records = {}; try { const conn = await pool.getConnection(); try { const query = ` SELECT ul.program, COUNT(*) as activecount, rl.total, (rl.total - COUNT(*)) as available FROM usedLicenses ul JOIN requestedLicenses rl ON ul.program = rl.name WHERE ul.date >= DATE_SUB(NOW(), INTERVAL ? SECOND) GROUP BY ul.program, rl.total `; const results = await conn.query(query, [n]); results.forEach(row => { records[row.program] = { activecount: String(row.activecount), total: String(row.total), available: String(row.available) }; }); } catch (error) { console.log("[ERROR]: There was an error reading from the database. More info: " + error); } finally { conn.release(); } } catch (error) { console.log("[ERROR]: There was an error reading from the database. More info: " + error); } return records; }; // Clients use this route to tell the server what programs they have open app.post('/api/currentPrograms', (req, res) => { //console.log('Post request received:', JSON.stringify(req.body)); const { applicationName, machineName, apiKey } = req.body; if(apiKey == token){ //Verify the client is one of our own addProgram(applicationName, machineName); } // Handle the request further if needed res.sendStatus(200); }); // Adds a program and machine name to the database const addProgram = async (programName, machineName) => { // Get the current date and format it for MariaDB datetime const date = new Date().toISOString().slice(0, 19).replace('T', ' '); try { const conn = await pool.getConnection(); try { // Check if a record with the same machineName and programName already exists const existingRecord = await conn.query('SELECT * FROM usedLicenses WHERE machine = ? AND program = ?', [machineName, programName]); if (existingRecord.length > 0) { // If record exists, update its timestamp await conn.query('UPDATE usedLicenses SET date = ? WHERE machine = ? AND program = ?', [date, machineName, programName]); //console.log("[SUCCESS]: Existing record updated in the database."); } else { // If no record exists, insert a new one await conn.query('INSERT INTO usedLicenses (program, machine, date) VALUES (?, ?, ?)', [programName, machineName, date]); //console.log("[SUCCESS]: New record added to the database."); } } finally { conn.release(); // Ensure the connection is released } } catch (error) { console.log("[ERROR]: There was an error adding/updating the record in the database. More info: " + error); } }; setTimeout(() => { testDatabaseConnection(); }, 1000); //Start the api and display the port it is running on const port = process.env.PORT || 3000; app.listen(port, () => { console.log(`**********************************`); console.log(`** **`); console.log(`* MPE License Tracker API *`); console.log(`** **`); console.log(`* ${version} *`); console.log(`** **`); console.log(`**********************************`); console.log(`API is running on port ${port}`); // Run the function to test the database connection //testDatabaseConnection(); //console.log(`Configured to use database IP ${dbip}`); //console.log(`Using user ${dbusername} for database`); });