UNPKG

loop-server-fast

Version:

A faster NodeJS reader to complement the AtomJump Loop Server.

1,359 lines (985 loc) 38.8 kB
#!/usr/bin/env node /* Fast Loop-Server reader - runs an http server which accepts requests like the PHP software, and quickly reads the results ready for display on-screen. To be used in the most common cases only (i.e. it doesn't handle bulk message downloads etc.) The loop-server config.json contains the settings for this server. Usage: node loop-server-fast.js config/path/config.json [-production] Testing https connection: openssl s_client -CApath /etc/ssl/certs -connect yourdomain.com:5566 Possible future project: Implement a mysql connection pool ala: https://codeforgeek.com/2015/01/nodejs-mysql-tutorial/ However, this may not be necessary - we already have multi-core handled by pm2, and we are one connection per database, but the requests are async. */ var multiparty = require('multiparty'); var http = require('http'); var https = require('https'); var util = require('util'); var path = require("path"); require("date-format-lite"); var mv = require('mv'); var fs = require('fs'); var exec = require('child_process').exec; var fsExtra = require('fs-extra'); try { var request = require("@cypress/request"); } catch(err) { var request = require("request"); } var needle = require('needle'); var readChunk = require('read-chunk'); // npm install read-chunk var async = require('async'); var mysql = require('mysql'); var os = require('os'); const querystring = require('querystring'); var crypto = require('crypto'); var get_ip = require('ipware')().get_ip; var httpsFlag = false; //whether we are serving up https (= true) or http (= false) var serverOptions = {}; //default https server options (see nodejs https module) var listenPort = 3277; //default listen port. Will get from the config readPort if it is set there var msg = {}; var lang; var timeUnits; //time units var agoDefault; //Usually in English set to 'ago' - the word displayed after the time units var verbose = false; var currentDbServer = []; currentDbServer[0] = 0; var usage = "Usage: node loop-server-fast.js config/path/config.json config/path/messages.json [-production]\n\nOr:\n\nnpm config set loop-server-fast:configFile /path/to/your/loop/server/config.json\nnpm config set loop-server-fast:messagesFile /path/to/your/loop/server/messages.json\n[npm config set loop-server-fast:production true]\nnpm run start\n\n"; var defaultPHPScript = "search-chat.php?"; var defaultPHPScriptLen = defaultPHPScript.length; var server; //Single global http or https server. process.on('SIGINT', function() { //Cleanly handle a process kill //See https://pm2.io/docs/runtime/best-practices/graceful-shutdown/ console.log("Requesting a shutdown."); server.close(); setTimeout(function() { // 1500ms later the process kill it self to allow a restart closeAllConnections(); console.log("Clean exit."); process.exit(0); }, 1500); }); process.on('uncaughtException', function (err) { console.log("Unhandled Exception, shutting down server ...") console.log(err); if(server) { server.close(); } setTimeout(function() { // 300ms later the process kill it self to allow a restart closeAllConnections(); console.log("Clean exit."); process.exit(0); }, 4000); }); if((process.argv)&&(process.argv[2])){ var loopServerConfig = process.argv[2]; } else { if(process.env.npm_package_config_configFile) { var loopServerConfig = process.env.npm_package_config_configFile; } else { console.log(usage); process.exit(0); } } var config = JSON.parse(fs.readFileSync(loopServerConfig)); if(!config) { console.log("Couldn't find config file " + loopServerConfig); process.exit(0); } if(((process.argv)&&(process.argv[3]))||(process.env.npm_package_config_messagesFile)){ //Get the messages and ago constants if(process.argv[3]) { var loopServerMessages = process.argv[3]; } else { //Get from the npm config var loopServerMessages = process.env.npm_package_config_messagesFile; } msg = JSON.parse(fs.readFileSync(loopServerMessages)); if(!msg) { console.log("Couldn't find messages file " + loopServerMessages); process.exit(0); } lang = msg.defaultLanguage; var time = msg.msgs[lang].time; timeUnits = [ { name: time.second, plural: time.seconds, limit: 60, in_seconds: 1 }, { name: time.minute, plural: time.minutes, limit: 3600, in_seconds: 60 }, { name: time.hour, plural: time.hours, limit: 86400, in_seconds: 3600 }, { name: time.day, plural: time.days, limit: 604800, in_seconds: 86400 }, { name: time.week, plural: time.weeks, limit: 2629743, in_seconds: 604800 }, { name: time.month, plural: time.months, limit: 31556926, in_seconds: 2629743 }, { name: time.year, plural: time.years, limit: null, in_seconds: 31556926 } ]; agoDefault = time.ago; } else { console.log(usage); process.exit(0); } if((process.argv[4]) && (process.argv[4] == '-production')){ var cnf = config.production; } else { if(process.env.npm_package_config_production) { if(process.env.npm_package_config_production == 'true') { var cnf = config.production; } else { var cnf = config.staging; } } else { var cnf = config.staging; } } //Configurable verbose variable if(process.env.npm_package_config_verbose) { if(process.env.npm_package_config_verbose == 'true') { verbose = true; } else { verbose = false; } } //Use the port specified in the config if(cnf.readPort) { listenPort = cnf.readPort; } //Create an https server if we specify a key and cert file if(cnf.httpsKey) { //httpsKey should point to the key .pem file httpsFlag = true; if(!serverOptions.key) { serverOptions.key = fs.readFileSync(cnf.httpsKey); console.log("https key loaded"); } } if(cnf.httpsCert) { //httpsCert should point to the cert .pem file httpsFlag = true; if(!serverOptions.cert) { serverOptions.cert = fs.readFileSync(cnf.httpsCert); console.log("https cert loaded"); } } var connections = []; var dbConnectionsInfo = []; var closing = false; //This is a global shutting down from mysql flag function closeAllConnections() { if(closing == false) { //Only do this once closing = true; for(var group = 0; group< connections.length; group++) { for(var ccnt = 0; ccnt< connections[group].length; ccnt++) { connections[group][ccnt].end(); console.log("Closed database connection [" + group + "][" + ccnt + "]"); } } } } function handleDisconnect(group, hostCnt) { //Check for a different database /* if((isset($db_cnf['scaleUp']))&&(isset($layer_name))) { //We are scaling up for($cnt = 0; $cnt< count($db_cnf['scaleUp']); $cnt ++) { if(preg_match($db_cnf['scaleUp'][$cnt]['labelRegExp'],$layer_name, $matches) == true) { //Override with this database $db_cnf = $db_cnf['scaleUp'][$cnt]; return; } } } */ //Inputs group: the 1st array index of connections[group][] // connection: the 2nd array index of connections[][connection] //Optional. If this is a single disconnection, then we can handle this case first and return. if(group != null) { //If not null //console.log("Group: " + group + " Host:" + hostCnt); //console.log("dbConnections : " + JSON.stringify(dbConnectionsInfo, null, 5)); if(!group) var group = 0; if(!hostCnt) var hostCnt = 0; var dbCnf = dbConnectionsInfo[group][hostCnt]; //End the old connection if it still exists. if(connections[group][hostCnt]) { connections[group][hostCnt].end(); } console.log("Attempting to re-connect to the database host [" + group + "][" + hostCnt + "]:" + dbCnf.host); connections[group][hostCnt] = mysql.createConnection({ host : dbCnf.host, user : dbCnf.user, password : dbCnf.pass, database : dbCnf.database, port : dbCnf.port, ssl : dbCnf.ssl, charset : "utf8mb4" }); var myHost = dbCnf.host; var myHostCnt = JSON.parse(JSON.stringify(hostCnt)); var myGroup = JSON.parse(JSON.stringify(group)); connections[myGroup][myHostCnt].connect(function(err) { // The server is either down var thisGroup = myGroup; var thisHostCnt = myHostCnt; if(err) { // or restarting (takes a while sometimes). console.log('Error when connecting to db [' + myGroup + '][' + myHostCnt + ']:' + myHost + ':', err); if(closing == false) { setTimeout(handleDisconnect, 5000, thisGroup, thisHostCnt); // We introduce a delay before attempting to reconnect, } } else { // to avoid a hot loop, and to allow our node script to console.log("Reconnected to " + myHost + " successfully."); console.log('Connected as id ' + connections[myGroup][myHostCnt].threadId); } }); // process asynchronous requests in the meantime. // If you're also serving http, display a 503 error. connections[myGroup][myHostCnt].on('error', function(err) { console.log('Db error on database [' + myGroup + '][' + myHostCnt + ']:', err); var thisGroup = myGroup; var thisHostCnt = myHostCnt; if(err.code === 'PROTOCOL_CONNECTION_LOST') { // Connection to the MySQL server is usually //Close and restart all the connections if(closing == false) { setTimeout(handleDisconnect, 5000, thisGroup, thisHostCnt); // lost due to either server restart, or a } } else { // connnection idle timeout (the wait_timeout //throw err; // server variable configures this) if(closing == false) { setTimeout(handleDisconnect, 5000, thisGroup, thisHostCnt); } } }); //And exit this reattempt. return; } //Reconnect to all db hosts console.log("Connecting to the database."); connections[0] = []; connections[0][0] = {}; dbConnectionsInfo[0] = []; dbConnectionsInfo[0][0] = {}; for(var cnt = 0; cnt< cnf.db.hosts.length; cnt++) { if(cnf.db.ssl && cnf.db.ssl.use === true) { var ssl = { ca : fs.readFileSync(cnf.db.ssl.capath) }; } else { var ssl = null; } console.log("Connecting to the main database host [0][" + cnt + "]:" + cnf.db.hosts[cnt]); dbConnectionsInfo[0][cnt] = { host : cnf.db.hosts[cnt], user : cnf.db.user, pass : cnf.db.pass, database : cnf.db.name, port : cnf.db.port, ssl : ssl, charset : "utf8mb4" }; connections[0][cnt] = mysql.createConnection({ host : cnf.db.hosts[cnt], user : cnf.db.user, password : cnf.db.pass, database : cnf.db.name, port : cnf.db.port, ssl : ssl, charset : "utf8mb4" }); } if(cnf.db.scaleUp) { //Create more connections for(var scaleCnt = 0; scaleCnt< cnf.db.scaleUp.length; scaleCnt++) { groupPlusOne = scaleCnt+1; connections[groupPlusOne] = []; dbConnectionsInfo[groupPlusOne] = []; currentDbServer[groupPlusOne] = 0; dbCnf = cnf.db.scaleUp[scaleCnt]; if(dbCnf.ssl && dbCnf.ssl.use === true) { var ssl = { ca : fs.readFileSync(dbCnf.ssl.capath) }; } else { var ssl = null; } for(var cnt = 0; cnt< dbCnf.hosts.length; cnt++) { console.log("Connecting to the scaleup database host [" + groupPlusOne + "][" + cnt + "]:" + dbCnf.hosts[cnt]); dbConnectionsInfo[groupPlusOne][cnt] = { host : dbCnf.hosts[cnt], user : dbCnf.user, pass : dbCnf.pass, database : dbCnf.name, port : dbCnf.port, ssl : ssl, charset : "utf8mb4" }; connections[groupPlusOne][cnt] = mysql.createConnection({ host : dbCnf.hosts[cnt], user : dbCnf.user, password : dbCnf.pass, database : dbCnf.name, port : dbCnf.port, ssl : ssl, charset : "utf8mb4" }); } } } //Now we have created all of the connections, we will connect to them asyncronously. //Create single array var singleDimConnections = []; for(var groupCnt = 0; groupCnt < dbConnectionsInfo.length; groupCnt++) { for(var hostCnt = 0; hostCnt < dbConnectionsInfo[groupCnt].length; hostCnt++) { singleDimConnections.push({ group: groupCnt, host: dbConnectionsInfo[groupCnt][hostCnt].host, hostCnt: hostCnt }); } } async.eachOf(singleDimConnections, // 2nd param is the function that each item is passed to function(runBlock, cnt, callback){ var myHost = runBlock.host; var myHostCnt = runBlock.hostCnt; var myGroup = runBlock.group; connections[myGroup][myHostCnt].connect(function(err) { // The server is either down var thisGroup = JSON.parse(JSON.stringify(myGroup)); var thisHostCnt = JSON.parse(JSON.stringify(myHostCnt)); if(err) { // or restarting (takes a while sometimes). //Error on trying to connect - try again in 2 seconds console.log('Error when connecting to main db [' + myGroup + '][' + myHostCnt + ']:', err); if(closing == false) { setTimeout(handleDisconnect, 2000, thisGroup, thisHostCnt); // We introduce a delay before attempting to reconnect, } } else { console.log('Connected to main db [' + thisGroup + '][' + thisHostCnt + '] OK'); console.log('Connected as id ' + connections[myGroup][myHostCnt].threadId); } // to avoid a hot loop, and to allow our node script to }); // process asynchronous requests in the meantime. // If you're also serving http, display a 503 error. connections[myGroup][myHostCnt].on('error', function(err) { console.log('Db error on main database [' + myGroup + '][' + myHostCnt + ']:', err); var thisGroup = JSON.parse(JSON.stringify(myGroup)); var thisHostCnt = JSON.parse(JSON.stringify(myHostCnt)); if(err.code === 'PROTOCOL_CONNECTION_LOST') { // Connection to the MySQL server is usually //Close and restart all the connections if(closing == false) { setTimeout(handleDisconnect, 2000, thisGroup, thisHostCnt); // lost due to either server restart, or a } } else { // connnection idle timeout (the wait_timeout //throw err; // server variable configures this) if(closing == false) { setTimeout(handleDisconnect, 2000, thisGroup, thisHostCnt); } } }); }, //End of async eachOf single item function(err){ // All tasks are done now if(err) { console.log('ERR:' + err); } else { console.log('Completed all database connections.'); } } ); //End of async eachOf all items if(closing == true) { //Have finished getting db connections console.log("Finished connecting to the database."); closing = false; } } handleDisconnect(null, null); function escapeRegExp(str) { return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); } function cleanData(str) { //Clean for database requests return escapeRegExp(str); } function trimChar(string, charToRemove) { if(string) { while(string.substring(0,1) == charToRemove) { string = string.substring(1); } while(string.slice(-1) == charToRemove) { string = string.slice(0, -1); } return string; } else { return null; } } function readSession(params, cb) { /* Sample session record | sgo3vosp1ej150sln9cvdslqm0 | 736 | 2016-06-09 16:04:03 | 2016-06-26 16:40:54 | view-count|i:1;logged-user|i:736;user-ip|s:15:"128.199.221.111";layer-group-user|s:0:"";authenticated-layer|s:3:"181";temp-user-name|s:7:"Anon 11";lat|i:51;lon|i:0; */ var keyValues = {}; if(verbose == true) console.log("SessionID" + cleanData(params.sessionId)); if(!params.connection) { //No connection cb(null); return; } params.connection.query("SELECT * FROM php_session WHERE session_id='" + cleanData(params.sessionId) + "'", function(err, rows, fields) { if (err) { console.log("Database error:" + err); cb(null); } else { if((rows[0])&&(rows[0].session_data)) { if(verbose == true) console.log("Session data:" + rows[0].session_data); var params = rows[0].session_data.split(";"); for(var cnt=0; cnt< params.length; cnt++) { var paramData = params[cnt].split("|"); if(paramData[1]) { //There is some data about this param var paramValues = paramData[1].split(":"); if(paramValues[0] == 'i') { //An integer - value proceeds var paramValue = paramValues[1]; } else { //A string, [1] is the string length, [2] is the string itself var paramValue = trimChar(paramValues[2], '"'); } keyValues[paramData[0]] = paramValue; if(verbose == true) console.log("Key:" + paramData[0] + " = " + paramValue); } } } cb(keyValues); } }); } function httpHttpsCreateServer(options) { try { if(httpsFlag == true) { console.log("Starting https server."); server = https.createServer(options, handleServer); //'server' is global } else { console.log("Starting http server."); server = http.createServer(handleServer); //'server' is global } server.listen(listenPort); //TODO process.send('ready'); //Let pm2 know we have started succesfully } catch(err) { //The server has some software or connection issue. Close all database connections //so that these don't build up. console.log(err); if(server) { server.close(); } closeAllConnections(); setTimeout(function() { //2 seconds later the process kill it self to allow a restart via pm2 (but not one which //is so quick that it instantly takes all other servers it is checking against down. console.log("Clean exit."); process.exit(0); }, 2000); } } function determineSubdomain(req) { if(req) { //Handle any subdomains in our if(verbose == true) console.log("Requesting: " + req.headers.host); //req.headers.host = eg. "outer.fast.atomjump.com" //"readURL" : "https://[subdomain]fast.atomjump.com", var myReadURL = cnf.readURL.replace("https://", ""); myReadURL = myReadURL.replace("http://",""); //Now myReadURL = e.g. "[subdomain]fast.atomjump.com" //req.headers.host = eg. "outer.fast.atomjump.com" //We want to find "outer". var noSubReadURL = myReadURL.replace("[subdomain]",""); //becomes e.g. .fast.atomjump.com var subdomain = req.headers.host.replace(noSubReadURL,""); //Strip off the non-subdomain url. Becomes outer. If we are at the same host e.g. atomjump.com, then the subdomain will be a blank string. if(verbose == true) console.log("Subdomain: " + subdomain); return subdomain; } else { return ""; } } function handleServer(_req, _res) { var req = _req; var res = _res; var body = []; var subdomain = determineSubdomain(req); //Start ordinary error handling req.on('error', function(err) { // This prints the error message and stack trace to `stderr`. console.error(err.stack); res.statusCode = 400; //Error during transmission - tell the app about it res.end(); }); req.on('data', function(chunk) { body.push(chunk); }); req.on('end', function() { //A get request to pull from the server // show a file upload form if(verbose == true) console.log("Requesting: " + req.url); //It must include search-chat.php or it is ignored. if(req.url.indexOf(defaultPHPScript) < 0) { res.writeHead(200, {'content-type': 'application/javascript'}); res.end("Not available"); return; } var url = req.url.substring(req.url.indexOf(defaultPHPScript) + defaultPHPScriptLen); //16 is length of 'search-chat.php'. We want the url as all the params after this. search-chat.php //is the current standard request entry point if(verbose == true) console.log("Parsed to query string:" + url); var params = querystring.parse(url); if(verbose == true) console.log("Query params = " + JSON.stringify(params)); var cookies = parseCookies(req); if(!params.sessionId) { //Only if not passed in on the command line params.sessionId = cookies.PHPSESSID; //This is our custom cookie. } if(!params.sessionId) { res.statusCode = 400; //Error - tell the app about it res.end(); console.log("Error: You must include a PHP Session ID"); return; } if(params.subdomain) { //Override the subdomain subdomain = params.subdomain; } var mylang = null; if(cookies.lang) { //If we are set in the cookies mylang = cookies.lang; } if(params.lang) { //Or set by a passed in parameter mylang = params.lang; } if(mylang) { //A language modifier exists var time = msg.msgs[mylang].time; params.timeUnits = [ { name: time.second, plural: time.seconds, limit: 60, in_seconds: 1 }, { name: time.minute, plural: time.minutes, limit: 3600, in_seconds: 60 }, { name: time.hour, plural: time.hours, limit: 86400, in_seconds: 3600 }, { name: time.day, plural: time.days, limit: 604800, in_seconds: 86400 }, { name: time.week, plural: time.weeks, limit: 2629743, in_seconds: 604800 }, { name: time.month, plural: time.months, limit: 31556926, in_seconds: 2629743 }, { name: time.year, plural: time.years, limit: null, in_seconds: 31556926 } ]; params.ago = time.ago; if(msg.msgs[mylang].numbers) { params.numbersTranslation = msg.msgs[mylang].numbers; //Get the number translation table for this language } } params.ip = getFakeIpAddress(params.sessionId); //Choose a random db connection params.connection = connections[0][currentDbServer[0]]; //Round robin the connection currentDbServer[0] ++; if(currentDbServer[0] >= cnf.db.hosts.length) currentDbServer[0] = 0; //Double up on this var myres = res; var jsonData = searchProcess(params, function(err, data) { if(err) { if(err == 'PHP') { //Call the PHP version of this script //Replace any 'fast' subdomains for the PHP request if(subdomain) { var replaceWith = subdomain + "."; } else { var replaceWith = ""; } var webRoot = cnf.webRoot.replace("[subdomain]", replaceWith); //strip out any subdomain codes from the url var fullUrl = webRoot + '/' + defaultPHPScript + url; if(verbose == true) console.log("Webroot:" + cnf.webRoot + " Default PHP script:" + defaultPHPScript + " Url:" + url + " fullUrl:" + fullUrl); callPHP(fullUrl, myres); return; } console.log(err); res.statusCode = 400; res.end(); } //Prepare the result set for the jsonp result var strData = params.callback + "(" + JSON.stringify( data ) + ")"; res.on('error', function(err){ //Handle the errors here res.statusCode = 400; res.end(); }) res.writeHead(200, {'content-type': 'application/javascript'}); res.end(strData, function(err) { //Wait until finished sending, then delete locally if(err) { console.log(err); } else { //success, do nothing process.stdout.write("."); //Show successful pings } }); }); //End of process }); //End of req end } function parseCookies (request) { var list = {}, rc = request.headers.cookie; rc && rc.split(';').forEach(function( cookie ) { var parts = cookie.split('='); list[parts.shift().trim()] = decodeURI(parts.join('=')); }); return list; } function showTranslatedNumber(number, translationTable) { /* PHP Function this is based on function show_translated_number($number, $lang) { //Input, ideally an integer between 0 - 60 (60 seconds in a minute means this //is usually the largest number we need for most things, i.e. 59 seconds ago). //However, any number can be used and the default is to return the English number //if there is no matching number in the messages array "number" conversion for the //input language. global $msg; if($msg['msgs'][$lang]['numbers']) { //Yes the numbers array exists for this language //Check definitely an integer if(is_int($number)) { if($msg['msgs'][$lang]['numbers'][$number]) { //Return the string if this is in the 'number' array as the indexed version of that number return $msg['msgs'][$lang]['numbers'][$number]; } else { return $number; } } else { return $number; } } else { return $number; } } */ if(translationTable) { if(Number.isInteger(number)) { if(translationTable[number]) { return translationTable[number]; } else { return number; } } else { return number; } } else { return number; } } function ago(timeStr, thisTimeUnits, agoWord, numbersTranslation) { var recTime = new Date(timeStr); var recTimeSecs = recTime.getTime(); //Get diff in seconds var nowSecs = new Date().getTime(); var diff = (nowSecs - recTimeSecs) / 1000; var i = 0; var unit = {}; while (unit = thisTimeUnits[i]) { if ((diff < unit.limit) || (!unit.limit)){ var diff = Math.round(diff / unit.in_seconds); //was floor diff = showTranslatedNumber(diff, numbersTranslation); var timeOut = diff + " " + (diff>1 ? unit.plural : unit.name) + " " + agoWord; return timeOut; } i++; } return "Unknown"; } function md5(data) { return crypto.createHash('md5').update(data).digest("hex"); } function getFakeIpAddress(sessionId) { //This is a copy of the PHP version - it creates an ip with 192.a.b.c where //a = ASCII value of sessionId's first character //b = ASCII value of sessionId's second character //c = ASCII value of sessionId's third character if(sessionId) { var ip = "192." + parseInt(sessionId.charCodeAt(0)) + "." + parseInt(sessionId.charCodeAt(1)) + "." + parseInt(sessionId.charCodeAt(2)); return ip; } else { //OK, so we don't have a session id - use a fixed random ip return "192.168.10.10"; } } function getRealIpAddress(req) { var ip_info = get_ip(req); return ip_info.clientIp.replace(/^[0-9.,]+$/,""); } function callPHP(url, res) { //Reads in from the PHP script url for a .jsonp response (plain text) //and write it out to the requester if(verbose == true) console.log("Redirecting to " + url); res.writeHead(302, { 'Location': url //add other headers here... }); res.end(); } function foundLayer(params, session, layer, ip, userCheck, initialRecords, outputJSON, debug, cb) { if((params.units) && (params.units != '')) { units = params.units; } if((params.dbg) && (params.dbg == 'true')) { debug = true; } else { debug = false; } if(session['logged-user']) { //OLDuserCheck = " OR int_author_id = " + session['logged-user'] + " OR int_whisper_to_id = " + session['logged-user']; userCheck = " OR int_author_id = ? OR int_whisper_to_id = ?"; } if(session['logged-group-user']) { //OLDuserCheck = userCheck + " OR int_author_id = " + session['logged-group-user'] + " OR int_whisper_to_id = " + session['logged-group-user']; userCheck = userCheck + " OR int_author_id = ? OR int_whisper_to_id = ?"; } if(params.records) { params.records = parseInt(params.records); } if((params.records) && (params.records < 100)) { initialRecords = 100; //min this can be - needs to be about 4 to 1 of private to public to start reducing the number of public messages visible } else { if(params.records) { initialRecords = params.records; } } //OLDvar sql = "SELECT * FROM tbl_ssshout WHERE int_layer_id = " + layer + " AND enm_active = 'true' AND (var_whisper_to = '' OR ISNULL(var_whisper_to) OR var_whisper_to ='" + ip + "' OR var_ip = '" + ip + "' " + userCheck + ") ORDER BY date_when_shouted DESC, int_ssshout_id DESC LIMIT " + initialRecords; var sql = "SELECT * FROM tbl_ssshout WHERE int_layer_id = ? AND enm_active = 'true' AND (var_whisper_to = '' OR ISNULL(var_whisper_to) OR var_whisper_to = ? OR var_ip = ? " + userCheck + ") ORDER BY date_when_shouted DESC, int_ssshout_id DESC LIMIT ?"; if(session['logged-group-user']) { var queryParams = [ layer, ip, ip, session['logged-user'], session['logged-user'], session['logged-group-user'], session['logged-group-user'], initialRecords ]; } else { if(session['logged-user']) { var queryParams = [ layer, ip, ip, session['logged-user'], session['logged-user'], initialRecords ]; } else { var queryParams = [ layer, ip, ip, initialRecords ]; } } if(verbose == true) console.log("Query: " + sql); if(!params.connection) { //check the connection is still valid //No connection cb(err, null); return; } params.connection.query(sql, queryParams, function(err, rows, fields) { if (err) { console.log("Database query error:" + err); cb(err, null); return; } outputJSON.res = []; outputJSON.ses = params.sessionId; var mymaxResults = rows.length; if(mymaxResults > (params.records + 1)) { //Return one more result than the record number, if available - this lets the js know there are more records, and it can then decide //to display the 'More' link or not. mymaxResults = (params.records + 1); //limit number to records requested var more = true; //show more flag for } var actualCnt = 0; //Exit early with no results on no layer access if((session['access-layer-granted'])&&(session['access-layer-granted'] !== "true") &&(session['access-layer-granted'] != true)) { if((session['access-layer-granted'] == 'false') || (session['access-layer-granted'] != layer)) { //See if we are in the array of layers, ignore this if((session['access-layers-granted'])&&(Array.isArray(session['access-layers-granted']))) { //Check if the layer is in, if not, then exit var granted = session['access-layers-granted']; if(granted.includes(layer)) { //No action needed } else { outputJSON.res = []; //No results cb(null, outputJSON); //No errors return; } } else { outputJSON.res = []; //No results cb(null, outputJSON); //No errors return; } } } //Time unit override var thisTimeUnits = timeUnits; //The default language var thisAgo = agoDefault; if(params.timeUnits) { //An override, which was set by the cookie 'lang' thisTimeUnits = params.timeUnits; thisAgo = params.ago; } if(params.numbersTranslation) { var numbersTranslation = params.numbersTranslation; } else { numbersTranslation = null; } for(var cnt = 0; cnt< rows.length; cnt++) { var whisper = true; //default var authorIP = rows[cnt].var_ip; var authorUserID = rows[cnt].int_author_id; var combinedAuthor = authorIP; if(authorUserID) { combinedAuthor = combinedAuthor + ":" + authorUserID; } var whisperToIP = rows[cnt].var_whisper_to; var whisperToUserID = rows[cnt].int_whisper_to_id; //If no whispering, or are whispering but to the viewer's ip address, or from the viewer's own ip if((whisperToIP == '')|| //ie. public ((whisperToIP == ip)&&(whisperToUserID == null))|| //private but no user id known (whisperToUserID == session['logged-user'])|| //talk direct to owner ((authorIP == ip)&&(authorUserID == null))|| //authored by this ip no user known of author (authorUserID == session['logged-user'])|| //authored by this viewer ((session['logged-group-user'] != "")&&(whisperToUserID != "") && (whisperToUserID == session['logged-group-user']))) { //private message to group //Right actually going to include message - now decide if whispering or public if(((whisperToIP == ip) && (whisperToUserID == null))|| //if it was a whisper intended for our ip but unknown user (whisperToUserID == session['logged-user'])|| //or a whisper specifically for us ((authorIP == ip) && ((whisperToIP != '')||(whisperToUserID)))|| //or def a whisper by viewer (authorUserID == session['logged-user'] && ((whisperToIP != '')|| (whisperToUserID)))) { //or a whisper by viewer logged in //This is a whisper to you or from you, use 1/3 font size whisper = true; } else { //A shout whisper = false; } if(session['logged-group-user']) { if(whisperToUserID == session['logged-group-user']) { whisper = true; } } if(!session['logged-user']) { //Force a blank user to see only public requests, until he has actually commented. whisper = false; } var shade = rows[cnt].int_ssshout_id %2; if(layer == 0) { //Public layer if(shade == 0) { bgcolor = "public-light"; } else { bgcolor = "public-dark"; } } else { //Private layer - different colours if(shade == 0) { bgcolor = "private-light"; } else { bgcolor = "private-dark"; } } //In PHP here? date_default_timezone_set($server_timezone); //E.g. "UTC" GMT" if(actualCnt <= mymaxResults) { var newEntry = { 'id': rows[cnt].int_ssshout_id, 'text': rows[cnt].var_shouted_processed, 'lat': rows[cnt].latitude, 'lon': rows[cnt].longtiude, 'dist': rows[cnt].dist, 'ago': ago(rows[cnt].date_when_shouted, thisTimeUnits, thisAgo, numbersTranslation), 'whisper': whisper } outputJSON.res.push(newEntry); actualCnt ++; //Increment the actual result count } } //End of valid message } //End of for loop if(verbose == true) console.log("Output: " + outputJSON); cb(null, outputJSON); //No errors }); //End of query } function checkScaleupHorizontally(layerName, params) { if(cnf.db.scaleUp) { //Create more connections for(var scaleCnt = 0; scaleCnt< cnf.db.scaleUp.length; scaleCnt++) { var regExp = new RegExp(cnf.db.scaleUp[scaleCnt].labelRegExp); if(layerName.search(regExp) >= 0) { //OK switch over to this db connection //Choose a random db connection params.connection = connections[scaleCnt+1][currentDbServer[scaleCnt+1]]; //Round robin the connection currentDbServer[scaleCnt+1] ++; if(currentDbServer[scaleCnt+1] >= cnf.db.scaleUp[scaleCnt].hosts.length) currentDbServer[scaleCnt+1] = 0; return; } } } return; } function searchProcess(params, cb) { //Get the session data checkScaleupHorizontally(params.passcode, params); //This should be before the 'readSession' because the session comes from this horiontal database also. readSession(params, function(session) { //eg. 'sgo3vosp1ej150sln9cvdslqm0' if(verbose == true) console.log("Finished getting session data. Logged user:" + session['logged-user']); if((session)&&(session['logged-user'])&&(session['logged-user'] != '')) { //Already logged in, but check if we know the ip address if((!session['user-ip'])||(session['user-ip'] == '')) { //No ip. Will have to revert back to the PHP version console.log('No ip. Going to PHP'); cb("PHP", null); return; } else { //We're good to make a db request //If this is the first request this session, we need to use the PHP //version to ensure we have registered the count if(session['view-count'] == 0) { console.log("view-count = " + session['view-count'] + " Going to PHP"); cb("PHP", null); return; } var layer = 1; var ip = params.ip; var userCheck = ""; var initialRecords = 100; var outputJSON = {}; var debug = false; if((params.passcode) && (params.passcode != '')||((params.reading) && (params.reading != ''))) { //See if we need to switch to a different db connection based off the layer name //OLDvar sql = "SELECT int_layer_id FROM tbl_layer WHERE passcode = '" + md5(params.passcode) + "'"; var sql = "SELECT int_layer_id FROM tbl_layer WHERE passcode = ?"; var queryParams = md5(params.passcode); if(params.connection) { //check the connection is still valid params.connection.query(sql, queryParams, function(err, rows, fields) { if(err) { console.log("Going back to PHP. Database error: " + err); cb("PHP", null); return; } else { if((rows)&&(rows[0])) { layer = rows[0].int_layer_id; foundLayer(params, session, layer, ip, userCheck, initialRecords, outputJSON, debug, cb); } else { console.log("No layer " + md5(params.passcode) + " - likely new. Going to PHP"); cb("PHP", null); return; //Unknown or new layer - head back to PHP } } }); } else { //The connection is no longer valid. We will be trying to reconnect in the background //but for now we need to show the user something console.log("Going back to PHP. Database error: " + err); cb("PHP", null); return; } } else { //End of passcode not = '' if(session['authenticated-layer']) { layer = session['authenticated-layer']; } else { layer = 1; //Default to about layer } foundLayer(params, session, layer, ip, userCheck, initialRecords, outputJSON, debug, cb); } } //End of do have an ip } else { //Not logged in - revert back to the PHP version console.log("Not logged in - back to PHP version"); cb("PHP", null); return; } }); //End of readSession } //Run at server startup httpHttpsCreateServer(serverOptions);