Initial commit

This commit is contained in:
2026-08-30 15:23:57 +02:00
commit dab5679098
1293 changed files with 240952 additions and 0 deletions
@@ -0,0 +1,68 @@
// ROOT/services/logger/readLogs.js
// Import the MySQL pools from your database service
const pools = require('../database/pools').get();
// Database name used for logging
const dbName = 'logging';
// Use the Read pool with the "system" role
const readPool = pools[dbName].r.s;
/*
* Function: getAllLogs
* Description: Retrieves all log entries from a specified table.
* @param {string} tableName - Name of the table to query.
* @returns {Array|Object} - Returns an array of log entries, or an error object.
*/
async function getAllLogs(tableName) {
if (!tableName) throw new Error('Table name is required');
try {
const [rows] = await readPool.query(`SELECT * FROM ${tableName}`);
return rows;
} catch (err) {
console.error('Error reading logs from DB:', err);
return { success: false, error: err };
}
}
/*
* Function: getLogs
* Description: Retrieves log entries from a specified table that match optional filter criteria.
* @param {string} tableName - Name of the table to query.
* @param {Object} filter - Optional key/value pairs representing column filters.
* Example: { user: 'andi', success: true }
* @returns {Array|Object} - Returns an array of filtered log entries, or an error object.
*/
async function getLogs(tableName, filter = {}) {
if (!tableName) throw new Error('Table name is required');
// Start building the SQL query
let sql = `SELECT * FROM ${tableName}`;
const values = [];
const filterKeys = Object.keys(filter);
if (filterKeys.length > 0) {
// Build WHERE clauses dynamically
const whereClauses = filterKeys.map(key => {
values.push(filter[key]);
return `${key} = ?`;
});
sql += ' WHERE ' + whereClauses.join(' AND ');
}
try {
const [rows] = await readPool.query(sql, values);
return rows;
} catch (err) {
console.error('Error reading filtered logs from DB:', err);
return { success: false, error: err };
}
}
// Export both functions for external use
module.exports = {
getAll: getAllLogs,
get: getLogs
};
@@ -0,0 +1,18 @@
let online = false;
module.exports = async function() {
try {
online = true;
console.log('Logger service started!');
return { online: online };
} catch (err) {
console.error('Logger service could not be started:', err);
return { online: false, error: err };
}
};
// Export online status for other modules
module.exports.online = () => online;
// Setter to change online status from other modules
module.exports.setOnline = (value) => { online = value; };
@@ -0,0 +1,5 @@
const logger = require('./start');
module.exports = async function() {
return { online: logger.online() };
};
@@ -0,0 +1,8 @@
const logger = require('./start');
module.exports = async function() {
// Set logger offline via setter in start.js
logger.setOnline(false);
console.log('Logger service stopped!');
return { online: logger.online() };
};
@@ -0,0 +1,56 @@
const loggerService = require('./start');
// Import the MySQL pools from your database service
const pools = require('../database/pools').get();
// Name of the database used for logging
const dbName = 'logging';
// Get the Write pool with the "system" role
const writePool = pools[dbName].w.s;
/*
* Generic function to write log data to any table
* @param {string} tableName - Name of the table to write to
* @param {Object} logData - Key/value pairs representing columns and values
* @returns {Object} - Success status and optional error
*/
async function writeToTable(tableName, logData) {
// Check if logger is active
if (!loggerService.online()) {
return { success: false, error: 'Logger is inactive' };
}
// Validate input
if (!tableName || !logData) throw new Error('Table name and log data are required');
// Extract columns and values dynamically from the logData object
const columns = Object.keys(logData).join(', '); // e.g., "user, action, success"
const placeholders = Object.keys(logData).map(() => '?').join(', '); // e.g., "?, ?, ?"
const values = Object.values(logData); // e.g., ["user", "login", true]
// Build the INSERT SQL statement
const sql = `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders})`;
try {
// Execute the query on the write pool
await writePool.query(sql, values);
// Return success
return { success: true };
} catch (err) {
// Log the error to the console for debugging
console.error('Error writing log to DB:', err);
// Return failure state
return { success: false, error: err };
}
}
/*
* Generic write function for any other log types.
* This allows dynamic handling of new log types without modifying the module.
*/
module.exports.write = async function(logData, tableName) {
return writeToTable(tableName, logData);
};
@@ -0,0 +1,25 @@
// variables
const services = { start: {}, stop: {}, restart: {}, status: {} };
const serviceNames = ['mysql','websocket','logger','scheduler'];
// dynamic manager
['start', 'stop', 'status'].forEach(action => {
serviceNames.forEach(name => {
services[action][name] = async () => {
const file = `./${name}/${action}.js`;
const func = require(file);
return await func();
};
});
});
// restart function
serviceNames.forEach(name => {
services.restart[name] = async () => {
await services.stop[name]();
await services.start[name]();
};
});
// function export
module.exports = services;
@@ -0,0 +1,191 @@
// node dependencies
const fs = require('fs');
const path = require('path');
// Path to the tasks.json file, which stores all tasks persistently
const tasksFile = path.join(__dirname, 'tasks.json');
module.exports = {
// In-memory object holding all tasks
/* Each task has the following structure:
tasks[taskName] = {
active: false, // whether the task is currently running
timer: null, // reference to setTimeout for execution
nextRun: null, // timestamp of next scheduled execution
lastRun: null, // timestamp of last execution
error: null, // last error if the task failed
oneTime: true/false, // if true, task runs only once
intervalInSec: 0 // interval in seconds for repeated execution
action: string // name of the JS file in ROOT/tasks
}
*/
tasks: {},
// Load tasks from tasks.json into memory
loadTasks: function() {
// Create empty file if missing
if (!fs.existsSync(tasksFile)) {
fs.writeFileSync(tasksFile, '{}', 'utf-8');
}
// Read and parse JSON
const rawData = fs.readFileSync(tasksFile, 'utf-8');
const data = JSON.parse(rawData);
// Load each task into memory
for (const name in data) {
const taskData = data[name];
// Ensure timer is reset in memory
taskData.timer = null;
this.tasks[name] = taskData;
// If task is active, automatically start the timer
if (taskData.active) {
this._startTimer(name);
}
}
},
// Save current in-memory tasks to tasks.json - Removes timer references before saving
saveTasks: function() {
const dataToSave = {};
for (const name in this.tasks) {
const task = { ...this.tasks[name] };
delete task.timer; // timers cannot be persisted
dataToSave[name] = task;
}
fs.writeFileSync(tasksFile, JSON.stringify(dataToSave, null, 4), 'utf-8');
},
// Add or update a task in memory and save it to JSON
// If the task does not exist, initialize default fields
setTask: function(name, data) {
// If task does not exist, create default structure
if (!this.tasks[name]) {
this.tasks[name] = {
active: false,
timer: null,
nextRun: null,
lastRun: null,
error: null,
oneTime: false,
intervalInSec: null,
action: null
};
}
// Merge new data
Object.assign(this.tasks[name], data);
// Stop any running timer before updating
if (this.tasks[name].timer) {
clearTimeout(this.tasks[name].timer);
this.tasks[name].timer = null;
}
// Immediately start the timer if task is active
if (this.tasks[name].active) {
this._startTimer(name);
}
// Persist to JSON
this.saveTasks();
},
// Remove a task completely from memory and JSON
removeTask: function(name) {
const task = this.tasks[name];
// Stop running timer if it exists
if (task && task.timer) clearTimeout(task.timer);
// Remove from memory
delete this.tasks[name];
// Persist changes
this.saveTasks();
},
// Retrieve a single task by name
getTask: function(name) {
return this.tasks[name] || null;
},
// Retrieve all tasks in memory
getAllTasks: function() {
return this.tasks;
},
/*
* Internal function to start the timer for a task
* - Loads the action from ROOT/tasks
* - Executes the function at intervals or once depending on task.oneTime
* @param {string} name - Task name
*/
_startTimer: function(name) {
const task = this.tasks[name];
// Task must exist and have an action defined
if (!task || !task.action) return;
// Build path to task action file
const taskPath = path.join(__dirname, '../../../tasks', task.action + '.js');
let actionFunc;
try {
// Load the exported function from the task file
actionFunc = require(taskPath);
if (typeof actionFunc !== 'function') {
throw new Error('Task action must export a function');
}
} catch (err) {
console.error(`Failed to load action for task "${name}":`, err.message);
task.error = err.message;
this.saveTasks();
return;
}
// Convert interval to milliseconds
const intervalMs = task.intervalInSec ? task.intervalInSec * 1000 : 0;
// Function executed by the timer
const executeTask = async () => {
task.lastRun = Date.now();
try {
// Call the action function
await actionFunc();
task.error = null;
} catch (err) {
task.error = err.message || String(err);
}
if (!task.oneTime && intervalMs && task.active) {
// Recurring task: schedule next run
task.nextRun = Date.now() + intervalMs;
task.timer = setTimeout(executeTask, intervalMs);
} else if (task.oneTime) {
// One-time task: deactivate after execution
task.active = false;
task.nextRun = null;
task.timer = null;
}
// Save updated task info
this.saveTasks();
};
// Activate the task and schedule first execution
task.active = true;
task.nextRun = Date.now() + intervalMs;
task.timer = setTimeout(executeTask, intervalMs);
// Save memory state
this.saveTasks();
}
};
@@ -0,0 +1,36 @@
const memory = require('./memory.js'); // Scheduler memory
module.exports = async function() {
try {
// Load tasks from tasks.json into memory
// Active tasks will automatically start their timers
memory.loadTasks();
// Build the return object with task status
const tasksStatus = {};
const allTasks = memory.getAllTasks();
for (const name in allTasks) {
const t = allTasks[name];
tasksStatus[name] = {
active: t.active,
oneTime: t.oneTime,
intervalInSec: t.intervalInSec,
lastRun: t.lastRun,
nextRun: t.nextRun,
error: t.error,
action: t.action
};
}
return {
online: true, // service is running
tasks: tasksStatus
};
} catch (err) {
console.error('Failed to start scheduler service:', err);
return {
online: false,
tasks: {}
};
}
};
@@ -0,0 +1,36 @@
const memory = require('./memory.js'); // Scheduler memory
module.exports = async function() {
try {
const allTasks = memory.getAllTasks();
// Build the return object with task status
const tasksStatus = {};
for (const name in allTasks) {
const t = allTasks[name];
tasksStatus[name] = {
active: t.active, // whether the task is currently marked active
oneTime: t.oneTime, // one-time or recurring
intervalInSec: t.intervalInSec,
lastRun: t.lastRun, // last execution timestamp
nextRun: t.nextRun, // next scheduled execution timestamp
error: t.error, // last error if occurred
action: t.action // linked action file
};
}
// Determine if service is online (any task has a timer running)
const online = Object.values(allTasks).some(t => t.timer);
return {
online: online,
tasks: tasksStatus
};
} catch (err) {
console.error('Failed to retrieve scheduler status:', err);
return {
online: false,
tasks: {}
};
}
};
@@ -0,0 +1,42 @@
const memory = require('./memory.js'); // Scheduler memory
module.exports = async function() {
try {
const allTasks = memory.getAllTasks();
// Stop all running timers without changing 'active' state
for (const name in allTasks) {
const task = allTasks[name];
if (task.timer) {
clearTimeout(task.timer);
task.timer = null;
}
}
// Build the return object with task status
const tasksStatus = {};
for (const name in allTasks) {
const t = allTasks[name];
tasksStatus[name] = {
active: t.active,
oneTime: t.oneTime,
intervalInSec: t.intervalInSec,
lastRun: t.lastRun,
nextRun: t.nextRun,
error: t.error,
action: t.action
};
}
return {
online: false, // service is stopped
tasks: tasksStatus
};
} catch (err) {
console.error('Failed to stop scheduler service:', err);
return {
online: false,
tasks: {}
};
}
};
@@ -0,0 +1 @@
{}
@@ -0,0 +1,86 @@
// node dependencies
const WebSocket = require('ws');
const fs = require('fs');
const path = require('path');
require('dotenv').config({ path: '../../.env' });
// internal dependencies
const connections = require('./connections.js'); // connection manager
const stopService = require('./stop.js');
module.exports = async function startWebsocketServer() {
loadActions(actionsDir);
try {
const wss = new WebSocket.Server({
host: process.env.HOST,
port: parseInt(process.env.WS_PORT)
});
stopService.setServer(wss);
console.log('WebSocket server started on host localhost and port 3001');
// Start heartbeat system
connections.startHeartbeat();
wss.on('connection', (ws, req) => {
if (key !== process.env.INTERNAL_SECRET || url.origin !== process.env.CONN_URI) {
ws.close();
return;
}
// Add client to connection manager
const connectionId = connections.add(ws, {
connectedAt: Date.now()
});
console.log('New client connected:', connectionId);
// Handle incoming messages
ws.on('message', async (raw) => {
let msg;
try {
msg = JSON.parse(raw);
} catch (err) {
console.error('Invalid JSON received:', raw);
return;
}
// Expected format:
// { action: "namespace.actionName", data: {...} }
if (!msg.action) return;
const actionHandler = actions[msg.action];
if (!actionHandler) {
console.warn('Unknown action:', msg.action);
return;
}
try {
await actionHandler(msg.data, msg.sec, ws, connectionId);
} catch (err) {
console.error('Error executing action:', msg.action, err);
}
});
// Remove connection on close
ws.on('close', () => {
console.log('Client disconnected:', connectionId);
connections.remove(connectionId);
});
// Remove connection on error
ws.on('error', (err) => {
console.error('WebSocket error on', connectionId, err);
connections.remove(connectionId);
});
});
return { online: true, server: wss };
} catch (err) {
console.error('WebSocket server failed to start:', err);
return { online: false, error: err };
}
};
@@ -0,0 +1,36 @@
// internal dependencies
const connections = require('./connections.js'); // connection manager
const stopService = require('./stop.js'); // for server reference
/*
* Get WebSocket service status
* Returns:
* {
* online: boolean,
* clients: {
* connectionId1: { metadata: {...}, lastHeartbeat: ... },
* connectionId2: { ... }
* }
* }
*/
module.exports = async function websocketStatus() {
try {
// check if server exists and is running
const serverOnline = !!stopService.getServer();
// get active clients
const clients = connections.getAll(); // object with connectionId -> { metadata, lastHeartbeat }
return {
online: serverOnline,
clients
};
} catch (err) {
console.error('Error getting WebSocket status:', err);
return {
online: false,
clients: {},
error: err
};
}
};
@@ -0,0 +1,42 @@
// internal dependencies
const connections = require('./connections.js');
// This will hold a reference to the running WebSocket server
let serverInstance = null;
/*
* Stop the WebSocket service
* - Stops heartbeat
* - Closes all active connections
* - Closes the WebSocket server
*/
async function stopService () {
try {
if (!serverInstance) return { online: false };
// Stop heartbeat system
connections.stopHeartbeat();
// Terminate all active connections
connections.clearAll();
// Close WebSocket server
await new Promise((resolve, reject) => {
serverInstance.close((err) => {
if (err) return reject(err);
resolve();
});
});
serverInstance = null;
console.log('WebSocket server stopped successfully');
return { online: false };
} catch (err) {
console.error('Error stopping WebSocket server:', err);
return { online: true, error: err };
}
};
module.exports = stopService;