diff --git a/.vscode/launch.json b/.vscode/launch.json index 73b7a5c..f5c514c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,11 +4,12 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { "type": "node", - "request": "launch", - "name": "Launch Program", - "program": "${workspaceFolder}/index.js" + "request": "attach", + "name": "Attach", + "port": 9229, } ] } \ No newline at end of file diff --git a/framework/db.js b/framework/db.js index adce832..13cc6a9 100644 --- a/framework/db.js +++ b/framework/db.js @@ -2,6 +2,4 @@ const {DB_SELECT} = require('../config'); if(DB_SELECT === 'json'){ module.exports = require('./jsondb'); -}else{ - module.exports = require('./mysqldb') } \ No newline at end of file diff --git a/framework/jsondb.js b/framework/jsondb.js index ae64684..0a1bf87 100644 --- a/framework/jsondb.js +++ b/framework/jsondb.js @@ -30,6 +30,22 @@ Database.prototype.createTables = async function() { } +Database.prototype.getMysqlConfigs = async function() { + try { + return await db.getData(`/mysqlConfig/`); + } catch (error) { + return null; + } +}; + +Database.prototype.setMysqlConfigs = async function(value) { + try { + await db.push(`/mysqlConfig/`, value, true); + } catch (error) { + return null; + } +}; + Database.prototype.getAllDomains = async function() { const domains = []; const domainPaths = await db.getData("/domains") || []; diff --git a/framework/mysqldb.js b/framework/mysqldb.js index 8c157fa..8205ca5 100644 --- a/framework/mysqldb.js +++ b/framework/mysqldb.js @@ -1,418 +1,60 @@ -const JsonDB = require("node-json-db"); const mysql = require('mysql'); -const bcrypt = require("bcrypt"); -const uuidv1 = require("uuid/v1"); -const logger = require("../framework/logger"); -const { DB_PATH, MYSQL_DATABASE, MYSQL_HOST, MYSQL_PASSWORD, MYSQL_USERNAME } = require('../config'); -const { Config } = require("node-json-db/dist/lib/JsonDBConfig"); +const _ = require('lodash') +const Logger = require('./logger') +const Database = require("./db"); -const db = new JsonDB(new Config(DB_PATH || "resource/database/jsonStore", true, false)); -let instance = null; +let connection; -const SALT_ROUNDS = 2; -const STATE = { - AUTHENTICATED: 'authenticated' -} - -const TABLES = { - DOMAINS: 'domains', - PATHS: 'paths' -} - -const Database = function() { - if (instance === null) { - instance = this; - } - this.connection = mysql.createConnection({ - host: MYSQL_HOST, - user: MYSQL_USERNAME, - password: MYSQL_PASSWORD, - database: MYSQL_DATABASE - }) - return this; -}; - -Database.prototype.connect = function() { - const connection = this.connection; - return new Promise(function(resolve, reject) { - connection.connect(function(err) { - if (err) { - logger.error(`Mysql Connection Error ${err}`); - return reject(err); - } - logger.info(`Mysql Connected ${connection.threadId}`); - return resolve(connection.threadId) - }); - }) -} - -Database.prototype.disconnect = function() { - const connection = this.connection; - return new Promise(function(resolve, reject) { - connection.end(function(err) { - if (err) { - logger.error(`Mysql Disconnection Error ${err}`); - return reject(err); - } - logger.info(`Mysql Disconnected ${connection.threadId}`); - return resolve(connection.threadId) - }); - }) -} - -Database.prototype.query = function(query) { - logger.info(query) - return new Promise(async(resolve, reject) => { - if (this.connection.state !== STATE.AUTHENTICATED) { - await this.connect() - } - this.connection.query(query, function(error, results, fields) { - if (error) { - logger.error(`Mysql Query Error ${error}`); - return reject(error); - }; - return resolve(results); - }); - }) - -} - -Database.prototype.createTables = async function() { - // domain table - return new Promise(async(resolve, reject) => { - try { - let query = `CREATE TABLE IF NOT EXISTS domains ( - id INT AUTO_INCREMENT PRIMARY KEY, - domainId VARCHAR(36) NOT NULL, - domainName VARCHAR(255) NOT NULL - ) ENGINE=INNODB;`; - - await this.query(query); - - query = `CREATE TABLE IF NOT EXISTS paths ( - id INT AUTO_INCREMENT PRIMARY KEY, - domainId VARCHAR(36) NOT NULL, - pathId VARCHAR(36) NOT NULL, - pathName VARCHAR(255) NOT NULL, - pathUrl VARCHAR(255) NOT NULL, - pathMethod VARCHAR(255) NOT NULL, - pathStatus VARCHAR(255) NOT NULL, - pathDescription VARCHAR(255) NULL, - header TEXT NOT NULL, - authentication BOOL, - body LONGTEXT NULL - ) ENGINE=INNODB;` - - await this.query(query); - - resolve(true); - } catch (error) { - logger.error(`Mysql Create tables Error ${err}`); - reject(false) - } - }) -} - -Database.prototype.rowExists = async function(tablename, filter) { - const results = await this.query(`SELECT * FROM ${tablename} WHERE ${filter}`); - return results; -} - -Database.prototype.addDomain = async function(domainName) { - if (!domainName) { - logger.error(`Domain Name NotINNER Found`); - return false; - } - const id = uuidv1(); +const getConnectionString = async function(database){ try { - const exists = await this.rowExists(TABLES.DOMAINS, `domainName='${domainName}'`); - if (exists.length <= 0) { - await this.query(`INSERT INTO ${TABLES.DOMAINS}(domainId,domainName) VALUES ('${id}','${domainName}')`) - return id; - } - return exists[0].domainId; + const config = await Database.getMysqlConfigs(); + return mysql.createConnection({ + host : config.host, + user : config.username, + password : config.password, + database : database + }); } catch (error) { - logger.error(` ${error}`); - return false; - } -}; - -Database.prototype.getAllDomains = async function() { - - const domains = []; - const domainPaths = await this.query(`SELECT domainId, domainName FROM ${TABLES.DOMAINS}`); - for (let i = 0; i < domainPaths.length; i++) { - const domain = domainPaths[i]; - const pathsCount = await this.query(`SELECT COUNT(pathId) as pathCount FROM ${TABLES.PATHS} WHERE domainId='${domain.domainId}'`); - domains.push({ - domainId: domain.domainId, - domainName: domain.domainName, - pathCount: pathsCount[0].pathCount - }) - } - return domains; -}; - -Database.prototype.getAllPaths = async function() { - const results = await this.query(`SELECT domains.domainId,domains.domainName,paths.pathId,paths.pathName,paths.pathUrl,paths.pathMethod,paths.pathStatus,paths.pathDescription,paths.header,paths.authentication,paths.body FROM ${TABLES.DOMAINS} as domains INNER JOIN ${TABLES.PATHS} as paths ON domains.domainId=paths.domainId`); - return results; -}; - -Database.prototype.getDomainFromId = async function(domainId) { - const domain = await this.query(`SELECT domainId, domainName FROM ${TABLES.DOMAINS} WHERE domainId='${domainId}'`) - if (domain.length > 0) { - return domain[0]; - } - return null; -}; - -Database.prototype.getPathNamesForDomain = async function(domainId) { - const pathNames = []; - const results = await this.query(`SELECT * FROM ${TABLES.PATHS} WHERE domainId = '${domainId}'`); - results.forEach(result => pathNames.push(result)); - return pathNames; -}; - -Database.prototype.updateDomainName = async function(domainId, domainName) { - await this.query(`UPDATE ${TABLES.DOMAINS} SET domainName ='${domainName}' WHERE domainId = '${domainId}'`); -}; - -Database.prototype.deleteDomain = async function(domainId) { - await this.query(`DELETE FROM ${TABLES.DOMAINS} WHERE domainId = '${domainId}'`); -}; - -Database.prototype.getPathsFromDomainId = async function(domainId) { - const results = await this.query(`SELECT domains.domainId,domains.domainName,paths.pathId,paths.pathName,paths.pathUrl,paths.pathMethod,paths.pathStatus,paths.pathDescription,paths.header,paths.authentication,paths.body FROM ${TABLES.DOMAINS} as domains INNER JOIN ${TABLES.PATHS} as paths ON domains.domainId=paths.domainId WHERE domains.domainId='${domainId}'`); - if (results.length > 0) { - return { - domainName: results[0].domainName, - paths: results - } - } - const domain = await this.getDomainFromId(domainId); - return { - domainName: domain.domainName, - paths: [] - } -} - -Database.prototype.addPath = async function(domainId, record, id = uuidv1()) { - const query = `INSERT INTO ${TABLES.PATHS}(domainId,pathId,pathName,pathUrl,pathMethod,pathStatus,pathDescription,header,authentication,body) values('${domainId}','${id}','${record.pathName}','${record.pathUrl}','${record.pathMethod}','${record.pathStatus}','${record.pathDescription}','${JSON.stringify(record.header)}',${record.authentication},'${record.body}')` - await this.query(query) - return id; -}; - -Database.prototype.getExistedPathId = async function({ domainName, pathUrl, pathMethod, pathStatus }) { - const results = await this.query(`SELECT domains.domainId,paths.pathId,paths.authentication - FROM ${TABLES.DOMAINS} as domains LEFT JOIN ${TABLES.PATHS} as paths - ON domains.domainId=paths.domainId - WHERE domains.domainName='${domainName}' AND paths.pathUrl = '${pathUrl}' AND paths.pathMethod = '${pathMethod}'`); - if (results.length > 0) { - return { - domainId: results[0].domainId, - pathId: results[0].pathId, - authentication: results[0].authentication === 0 ? false : true - } - } else { - return {} + return null; } } -Database.prototype.getPath = async function(domainId, pathId) { - const results = await this.query(`SELECT domains.domainId,domains.domainName,paths.pathId,paths.pathName,paths.pathUrl,paths.pathMethod,paths.pathStatus,paths.pathDescription,paths.header,paths.authentication,paths.body FROM ${TABLES.DOMAINS} as domains INNER JOIN ${TABLES.PATHS} as paths ON domains.domainId=paths.domainId WHERE domains.domainId='${domainId}' AND paths.pathId = '${pathId}'`); - if (results.length > 0) { - return { - domainName: results[0].domainName, - paths: results - } - } - const domain = await this.getDomainFromId(domainId); - if (domain === null) { - return { - domainName: null, - paths: [] - } - } - return { - domainName: domain.domainName, - paths: [] - } -} - -Database.prototype.updatePath = async function(domainId, pathId, record) { - const query = `UPDATE ${TABLES.PATHS} SET - pathName='${record.pathName}', - pathUrl='${record.pathUrl}', - pathMethod='${record.pathMethod}', - pathStatus='${record.pathStatus}', - pathDescription='${record.pathDescription}', - header='${JSON.stringify(record.header)}', - authentication=${record.authentication}, - body='${record.body}' WHERE domainId='${domainId}' AND pathId='${pathId}'` - await this.query(query); -}; - -Database.prototype.deletePath = async function(domainId, pathId) { - await this.query(`DELETE FROM ${TABLES.PATHS} WHERE domainId = '${domainId}' AND pathId = '${pathId}'`); -}; - -Database.prototype.getPathsForDomain = function(domainId) { - return db.getData(`/domains[${domainId}]`).paths; -}; - -Database.prototype.deleteAllUsers = function(domainId, pathId, record) { - db.delete(`/users`); -}; - -Database.prototype.deleteUsers = function(id) { - - db.delete(`/users[${id}]`); -}; - -Database.prototype.setUser = function(username, password, userEmail, id) { - bcrypt.hash(password, SALT_ROUNDS, function(err, hash) { - if (err) { - throw new Error(err); +const connect = async function(database){ + try { + if(_.get(connection,'state','disconnected') === 'authenticated'){ + return; } - const user = { - id: id || uuidv1(), - username, - userEmail, - password: hash - }; - db.push(`/users[]`, user, true); - }); -}; - -Database.prototype.getAllUsers = function() { - return db.getData("/users"); -}; - -const checkValidity = async function(username, password, user) { - return bcrypt - .compare(password, user.password) - .then(function(res) { - if (res) { - if (user.username === username) { - return { - username, - action: true - }; - } - return { - username, - action: false - }; - } - }) - .catch(function(error) { - throw new Error(error); + connection = await getConnectionString(database); + await connection.connect(); + connection.on('error', function(err) { + Logger.error("[mysql error]",err); }); -}; - -Database.prototype.getUser = async function(username, password) { - const users = db.getData(`/users`); - - let userFound = false; - let userId = null; - let counter = 0; - for (user of users) { - counter = counter + 1; - try { - const userinfo = await checkValidity(username, password, user); - if (!userinfo.action) { - continue; - } - userFound = true; - userId = user.id; - break; - } catch (error) { - continue; - } - } - if (userFound) { - return { - userId, - username, - counter, - action: true - }; - } - return { - userId, - username, - counter, - action: false - }; -}; - -Database.prototype.getUserFromUsername = async function(username) { - const users = db.getData(`/users`); - let counter = 0; - for (user of users) { - if (user.username === username) { - return { - ...user, - counter - } - } - counter = counter + 1; + } catch (error) { + return error.message } - return null; -}; - -Database.prototype.saveCustomCommand = function(key, value) { - db.push(`/userCommands/${key}/`, value, true); - return ""; -}; - -Database.prototype.getCustomCommand = function(key, value) { - return db.getData(`/userCommands/${key}/`); -}; - -Database.prototype.delCustomCommand = function(key, value) { - db.delete(`/userCommands/${key}/`); - return ""; -}; - -Database.prototype.flushAllUserData = function(data) { - db.delete(`/userCommands/`); } - -Database.prototype.setEnableUpload = function(data) { - db.push('/upload/', data, true) +const disconnect = async function(){ + await connection.end(); } -Database.prototype.getEnableUpload = function(data) { - return db.getData('/upload/') +const reconnect = async function(){ + await disconnect(); + await connect(); } -Database.prototype.saveToken = function(token) { - db.push(`/authToken/`, token, true); -}; - -Database.prototype.getToken = function(token) { - return db.getData(`/authToken/`); -}; - -Database.prototype.saveApiUrl = function(url) { - db.push(`/apiUrl/`, url, true); -}; - -Database.prototype.getApiUrl = function() { - return db.getData(`/apiUrl/`); -}; - -Database.prototype.saveResetToken = function(token) { - db.push(`/resetToken/`, token, true); -}; - -Database.prototype.getResetToken = function() { - return db.getData(`/resetToken/`); -}; -Database.prototype.deleteResetToken = function() { - db.delete(`/resetToken/`); +const query = async function(q){ + return new Promise((resolve,reject)=>{ + connection.query(q, function (error, results) { + if (error) return reject(error); + resolve(results); + }) + }) }; -module.exports = new Database(); \ No newline at end of file +module.exports = { + connect, + disconnect, + reconnect, + query +} \ No newline at end of file diff --git a/framework/server.js b/framework/server.js index 219d060..85d49a3 100644 --- a/framework/server.js +++ b/framework/server.js @@ -73,6 +73,10 @@ Server.prototype.init = async function(port) { this.status = 'Started'; }; +Server.prototype.getPort = function(){ + return this.port; +} + Server.prototype.sendData = function (data) { try { this.wsInstance.getWss().clients.forEach(function each(ws) { @@ -199,19 +203,28 @@ async function execProgCommand(match, response) { const underscore = require('underscore'); const faker = require('faker'); const uuidv4 = require('uuid/v4'); - ${params}`; + const mysql = require('./mysqldb'); + try{ + ${params} + }catch(e){ + return JSON.stringify(e); + }`; const AsyncFunction = Object.getPrototypeOf(async function() {}).constructor; - const value = await new AsyncFunction( - 'exports', - 'require', - 'module', - '__filename', - '__dirname', - params - )(exports, require, module, __filename, __dirname); - return returnResponse.replace(/#prog_value#/g, value); + try { + const value = await new AsyncFunction( + 'exports', + 'require', + 'module', + '__filename', + '__dirname', + params + )(exports, require, module, __filename, __dirname); + return returnResponse.replace(/#prog_value#/g, value); + } catch (error) { + return returnResponse.replace(/#prog_value#/g, JSON.stringify(error)); + } } function execForCommand(match) { diff --git a/index.js b/index.js index 2de85da..6b428f4 100644 --- a/index.js +++ b/index.js @@ -4,10 +4,12 @@ const _ = require("lodash"); const bodyParser = require("body-parser"); const cookieParser = require("cookie-parser"); const session = require("express-session"); +const nodeadmin = require('nodeadmin'); const logger = require("./framework/logger"); const domainRouter = require("./routes/domainRouter"); const pathRouter = require("./routes/pathRoutes"); const schedulers = require("./routes/shedulers"); +const otherRoutes = require("./routes/other"); const variables = require("./routes/variables"); const socketRouter = require("./routes/socket"); const uuidv1 = require("uuid/v1"); @@ -22,7 +24,6 @@ const systemApp = express(); systemApp.set("view engine", "ejs"); systemApp.use(express.static("public")); - systemApp.use(cookieParser()); systemApp.use( session({ @@ -63,7 +64,6 @@ systemApp.use((req, res, next) => { }); const sessionChecker = (req, res, next) => { - return next(); if (req.session.user && req.cookies.userId) { next(); } else { @@ -205,6 +205,15 @@ systemApp.post("/flushAll", function(req, res) { } }); +systemApp.post("/saveMysqlConfig", function(req, res) { + try { + db.setMysqlConfigs(req.body); + res.send({ success: true }); + } catch (error) { + res.send({ success: false }); + } +}); + systemApp.post("/upload", async function(req, res) { const isEnable = db.getEnableUpload().enable == "true"; if (!isEnable) { @@ -342,12 +351,14 @@ systemApp.post("/upload", async function(req, res) { systemApp.use("/domain", sessionChecker, domainRouter); systemApp.use("/domain/paths", sessionChecker, pathRouter); systemApp.use("/schedulers", sessionChecker, schedulers); +systemApp.use("/other",sessionChecker,otherRoutes); systemApp.use("/variables", sessionChecker, variables); systemApp.use("/sockets",sessionChecker,socketRouter); (async function() { await db.createTables(); Server().app.use(ADMIN_PREFIX, systemApp); + Server().app.use(nodeadmin(Server().app)); Server().app.get('/', (req, res) => res.redirect(`${ADMIN_PREFIX}/`)) await Server().init(port); })() diff --git a/package.json b/package.json index 8a483ce..62a86ed 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "node index.js" + "start": "node index.js", + "debug": "node --inspect index.js" }, "keywords": [ "mockable", @@ -14,7 +15,7 @@ "author": "Kolitha Warnakulasooriya", "license": "MIT", "dependencies": { - "bcrypt": "2.0.0", + "bcrypt": "^4.0.1", "body-parser": "^1.18.3", "cookie-parser": "^1.4.4", "cors": "^2.8.5", @@ -30,6 +31,7 @@ "node-fetch": "^2.4.1", "node-json-db": "^0.11.0", "node-schedule": "^1.3.2", + "nodeadmin": "^0.1.2", "nodemailer": "^6.3.0", "path": "^0.12.7", "underscore": "^1.9.1", diff --git a/public/scripts/viewDomain.js b/public/scripts/viewDomain.js index abe33fd..fadec21 100644 --- a/public/scripts/viewDomain.js +++ b/public/scripts/viewDomain.js @@ -9,6 +9,36 @@ function onSchedulerClick(){ window.location.href = `/admin/schdulers`; } +function configureMysql(){ + const data = { + host: document.getElementById("host").value || "localhost:3306", + username: document.getElementById("username").value || "root", + password: document.getElementById("password").value || "password" + } + + $.ajax({ + type: 'POST', + url: '/admin/saveMysqlConfig', + data + }); +} + +function handleOnNodeAdmin(){ + $.ajax({ + type: 'GET', + url: '/admin/other/nodeAdmin', + success: function(data) { + window.localStorage.setItem('nodeadmin', data.token); + var win = window.open(data.reload, '_blank'); + win.focus(); + }, + error: function(data) { + var win = window.open(data.reload, '_blank'); + win.focus(); + } + }); +} + function onEnvironmentClick(){ window.location.href = `/admin/variables`; } diff --git a/readme.md b/readme.md index d0038c4..72a3edc 100644 --- a/readme.md +++ b/readme.md @@ -515,6 +515,60 @@ You can change your response by query parameter without do any if else syntax. t Templete 1 will received for testDomain1/testPath1?templete=templete1 Templete 2 will received for testDomain1/testPath1?templete=templete2 +## MySQL Database +Mockabale express created endpoints can directly communicated with MYSQL database. so in #prog# mode. As the result, data can be manupulated thought a database. + +### Setup Configurations +You have to setup mysql connection in mockable express server. you have to click on the right side dropdown and click on the configure Mysql button. Then you can see a configuration window. enter your mysql database host, username and password. + +### Connect to Mysql Database + +You can use **mysql.connect** to connect database + +``` +#prog{ + try{ + await mysql.connect("test"); + // rest of query + }catch(e){ + return JSON.stringify(e); + } +}endprog +#prog_value# +``` +### Execute Query +After connection created, you can execute SQL query using **mysql.query** function +``` +#prog{ + try{ + await mysql.connect("test"); + const data = await mysql.query('SELECT * FROM data WHERE id={{id}}') + return JSON.stringify(data) + }catch(e){ + return JSON.stringify(e); + } +}endprog +#prog_value# +``` + +### Disconnect Mysql +``` +#prog{ + try{ + await mysql.connect("test"); + const data = await mysql.query('SELECT * FROM data WHERE id={{id}}') + await mysql.disconnect(); + return JSON.stringify(data) + }catch(e){ + return JSON.stringify(e); + } +}endprog +#prog_value# +``` + +### Mysql Node Admin viewer +Click on **nodeadmin** button to go nodeAdmin mysql viewer. + ## Limitations 1. Query params should not contain '+' charactor. is it is necessary, it should be changed in the conditions as ' ' diff --git a/routes/other.js b/routes/other.js new file mode 100644 index 0000000..9aff904 --- /dev/null +++ b/routes/other.js @@ -0,0 +1,44 @@ +const express = require("express"); +const fetch = require('node-fetch'); +const Server = require('../framework/server'); +const otherRouter = express.Router(); +const Database = require("../framework/db"); +const Logger = require("../framework/logger"); +const {HOST} = require("../config"); + +// view domains +otherRouter.get("/nodeAdmin", async function(req, res) { + const mysqlConfigs = await Database.getMysqlConfigs(); + + if(!mysqlConfigs){ + return res.redirect('/nodeadmin') + } + + const url = `http://${HOST}:${Server().getPort()}/nodeadmin/api/auth/login` + const reload = `http://${HOST}:${Server().getPort()}/nodeadmin/#!/db`; + const body = { + mysqlUser: mysqlConfigs.username, + mysqlPassword: mysqlConfigs.password, + mysqlHost:mysqlConfigs.host + } + try { + const response = await fetch(url,{method: "POST", body: JSON.stringify(body), headers: { + 'Content-Type': 'application/json' + }}); + const data = await response.json(); + if(data.token){ + return res.json({ + reload, + token: data.token + }); + } + } catch (error) { + Logger.error(error); + } + return res.json({ + reload + }); +}); + + +module.exports = otherRouter; \ No newline at end of file diff --git a/views/domain/viewDomain.ejs b/views/domain/viewDomain.ejs index 0b8f7af..b8041f2 100644 --- a/views/domain/viewDomain.ejs +++ b/views/domain/viewDomain.ejs @@ -55,6 +55,9 @@
Sockets
+
+ Node Admin +
@@ -77,6 +80,7 @@ +
@@ -106,6 +110,38 @@ + +