Creating my own sql wrapper for nodejs/express - javascript

I'm trying to create my own wrapper for mysql for my nodejs application. I have two questions here one of which theres a work around and one where I'm unsure what to do as my javascript skills are still in the learning phase.
First thing: As of right now when you navigate to /api/finance it directs you to the finance controller and the index method. This is currently just for testing purposes trying to figure out how to this kind of stuff.
FinanceController:
const sql = require('../../sql.js')
module.exports = {
index: (req, res, next) => {
sql.get('test').then((result) => {
res.status(200).json(result);
})
}
}
sql.js
const mysql = require('mysql');
const { DB } = require('./config')
var connection = mysql.createConnection(DB)
module.exports = {
get: function(table, columns = '*') {
return new Promise((resolve, reject) => {
connection.query('SELECT ?? FROM ?? ', [columns, table], function (error, results, fields) {
if (error) throw error;
resolve(results);
});
})
},
all: function(table) {
return new Promise((resolve, reject) => {
connection.query('SELECT * FROM ?? ', table, function (error, results, fields) {
if (error) throw error;
resolve(results);
});
})
},
where: function(){
console.log('where called')
}
}
As you can see, I have a get() and all(). get() allows you to pass the table name and an array of columns for example: ['id', 'name'] would get you the id column and name column. columns = '*' was an attempt on being able to use one function to either get all columns of the table or specify specific columns however it returns an error: Unknown column in 'field list' so all() was my "workaround" however i'd like it to be one function.
Next I can't figure out how to stack/pipe methods? if thats the word.
The goal here would be so I could call the function like this:
index: (req, res, next) => {
sql.all('test').where().then((result) => {
res.status(200).json(result);
})
}
}
obviously within the .where() I would have it like: .where('id', '=', 'userID') or something along those lines.
however I'm unsure on how to go about doing that and would like some guidance if its possible. I receive the error: sql.all(...).where is not a function

Instead of immediately launching the SQL, you should simply register the provided information in an instance object (having the same methods) and return that object, and let each method enrich the SQL until the end of the chain is reached and you call a method that will launch the SQL.
The object that is passed from one method to the next (as this) maintains state, and collects the different elements of the SQL statement.
Here is one way to do it.
NB: In this demo I used a mock-object for connection. This mock object will not actually connect to a database. Its query method will just produce the final SQL (with all ? resolved) instead of a real data set.
// Mock connection object for this snippet only. Just produces the final SQL, not the result
var connection = {
query(sql, params, callback) {
let i = 0;
sql = sql.replace(/\?\??/g, (m) => {
if (m.length > 1) return [].concat(params[i++]).map(p => "`" + p + "`").join(", ");
if (typeof params[i] === "string") return "'" + params[i++].replace(/'/g, "''") + "'";
return params[i++];
});
setTimeout(callback(null, sql));
}
}
// Function to create an instance when a first method call is made on the `sql` object directly
// Chained method calls will keep using that instance
function getInstance(inst) {
if (inst.columns) return inst; // Keep using same instance in the chain
inst = Object.create(inst); // No instance yet: create one
inst.table = null;
inst.params = [];
inst.columns = [];
inst.conditions = [];
inst.order = [];
return inst;
}
// This sql object serves a starting point as well
// as proto object for the instance object that
// passes through the chain:
var sql = {
from(table) {
let inst = getInstance(this);
inst.table = table;
return inst;
},
select(...columns) {
let inst = getInstance(this);
inst.columns = inst.columns.concat(columns);
return inst;
},
where(column, cmp, value) {
if (!["<",">","<=",">=","=","!="].includes(cmp)) throw "invalid operator";
let inst = getInstance(this);
inst.params.push(column, value);
inst.conditions.push(cmp);
return inst;
},
orderBy(...columns) {
let inst = getInstance(this);
inst.order = inst.order.concat(columns);
return inst;
},
promise() {
if (!this.table) throw "no table specified";
// build SQL and parameter list
let sql = "SELECT *";
let params = [];
if (this.columns.length && this.columns != "*") {
sql = "SELECT ??";
params.push(this.columns);
}
sql += " FROM ??";
params.push(this.table);
if (this.conditions.length) {
sql += " WHERE " + this.conditions.map(cmp => `?? ${cmp} ?`).join(" AND ");
params.push(...this.params);
}
if (this.order.length) {
sql += " ORDER BY ??";
params.push(this.order);
}
return new Promise(resolve => {
connection.query(sql, params, function (error, results) {
if (error) throw error;
resolve(results);
});
});
}
};
// demo
sql.from("customer")
.select("id", "name")
.where("name", ">", "john")
.where("name", "<", "thomas")
.orderBy("name", "id")
.promise()
.then(console.log);
Note that in this implementation it does not matter in which order you chain the from, select, where and order method calls. You could even do the following if you wanted to:
sql .orderBy("name", "id")
.where("name", ">", "john")
.from("customer")
.where("name", "<", "thomas")
.select("id", "name")
.promise()
.then(console.log);

Related

How to use filepath in extractIssueData?

I am using request NPM package and request take two parameters
request (URL, callback);
Here I somehow want to pass extra argument to my callback function how do I do it.
This is the code I am trying to write
function extractIssues(url,filepath){
request(url,issueCb);
}
function issueCb(err,response,html)
{
if(err){
console.log(err+"at line 61");
}
else{
extractIssueData(html);
}
}
function extractIssueData(html){
let selTool = cheerio.load(html);
let arr = [];
let issueLinksArr = selTool(".flex-auto.min-width-0.p-2.pr-3.pr-md-2 > a");
let result="";
for(let i = 0;i<issueLinksArr.length;i++){
let issueLink = selTool(issueLinksArr[i]).attr("href");
let content = selTool(issueLinksArr[i]).text().trim().split("/n")[0];
let obj = {
link :issueLink,
content:content
}
let str = JSON.stringify(obj);
result = result + str + " ,"+ "\n" ;
}
console.log(result);
}
I want to use filepath in extractIssueData so I need to first catch it in issueCb how do I do it
I can't find proper answer.
This issue disappears entirely if you simplify to use a single function! The following should be equivalent to, if not an improvement on, your code:
let extractIssues = async (url, filepath) => {
// Wait for the html content of a `request` call
let html = await new Promise((resolve, reject) => {
// If an Error occurs reject with the error, otherwise resolve with
// html data
request(url, (err, res, html) => err ? reject(err) : resolve(html));
});
let selTool = cheerio.load(html);
// Collect href and text content values from <a> elements
let resultsArr = selTool(".flex-auto.min-width-0.p-2.pr-3.pr-md-2 > a")
.map(issueLink => ({
link: selTool(issueLink).attr("href"),
content: selTool(issueLink).text().trim().split("/n")[0]
}));
// Print the array of link+content data
console.log(resultsArr);
};
Note that within this function you have access to filepath, so you can use it as is necessary.
A solution without async looks like:
let extractIssues = (url, filepath) => {
request(url, (err, res, html) => {
if (err) throw err;
let selTool = cheerio.load(html);
// Collect href and text content values from <a> elements
let resultsArr = selTool(".flex-auto.min-width-0.p-2.pr-3.pr-md-2 > a")
.map(issueLink => ({
link: selTool(issueLink).attr("href"),
content: selTool(issueLink).text().trim().split("/n")[0]
}));
// Print the array of link+content data
console.log(resultsArr);
});
};
Once again this creates a scenario where filepath is available to be used as necessary within the function.
Note: If you are being "given instruction" not to use async you are being done a disservice; async is a core feature and major selling point of javascript. Once you become comfortable with async you will quickly regret ever having worked without it. I recommend you take the time to understand it! :)
I can create you an easy sample to use callback functions.
It's a stupid example I know but it helps you to understand how callbacks are working.
function myFunc(callback) {
let errMessage = "This is error callback";
let message = "This is normal message";
callback(errMessage, message);
}
function myCallbackFunc(err, result) {
if(!err) {
console.log(result);
return;
}
console.log(err);
}
myFunc(myCallbackFunc);
You won't use it like this but If you want to send a error callback, you should null to response param. Like this:
function myFunc(callback) {
let errMessage = "This is error callback";
let message = "This is normal message";
let somethingBadHappened = true;
if(somethingBadHappened) {
return callback(errMessage, null);
}
callback(null, message)
}
function myCallbackFunc(err, result) {
if(!err) {
console.log(result);
return;
}
console.log(err);
}
myFunc(myCallbackFunc);

Node.js mssql return query result to ajax

I'm new to learning Node.js, so I'm still getting used to asynchronous programming and callbacks. I'm trying to insert a record into a MS SQL Server database and return the new row's ID to my view.
The mssql query is working correctly when printed to console.log. My problem is not knowing how to properly return the data.
Here is my mssql query - in addJob.js:
var config = require('../../db/config');
async function addJob(title) {
var sql = require('mssql');
const pool = new sql.ConnectionPool(config);
var conn = pool;
let sqlResult = '';
let jobID = '';
conn.connect().then(function () {
var req = new sql.Request(conn);
req.query(`INSERT INTO Jobs (Title, ActiveJD) VALUES ('${title}', 0) ; SELECT ##IDENTITY AS JobID`).then(function (result) {
jobID = result['recordset'][0]['JobID'];
conn.close();
//This prints the correct value
console.log('jobID: ' + jobID);
}).catch(function (err) {
console.log('Unable to add job: ' + err);
conn.close();
});
}).catch(function (err) {
console.log('Unable to connect to SQL: ' + err);
});
// This prints a blank
console.log('jobID second test: ' + jobID)
return jobID;
}
module.exports = addJob;
This is my front end where a modal box is taking in a string and passing it to the above query. I want it to then receive the query's returned value and redirect to another page.
// ADD NEW JOB
$("#navButton_new").on(ace.click_event, function() {
bootbox.prompt("New Job Title", function(result) {
if (result != null) {
var job = {};
job.title = result;
$.ajax({
type: 'POST',
data: JSON.stringify(job),
contentType: 'application/json',
url: 'jds/addJob',
success: function(data) {
// this just prints that data is an object. Is that because I'm returning a promise? How would I unpack that here?
console.log('in success:' + data);
// I want to use the returned value here for a page redirect
//window.location.href = "jds/edit/?jobID=" + data;
return false;
},
error: function(err){
console.log('Unable to add job: ' + err);
}
});
} else {
}
});
});
And finally here is the express router code calling the function:
const express = require('express');
//....
const app = express();
//....
app.post('/jds/addJob', function(req, res){
let dataJSON = JSON.stringify(req.body)
let parsedData = JSON.parse(dataJSON);
const addJob = require("../models/jds/addJob");
let statusResult = addJob(parsedData.title);
statusResult.then(result => {
res.send(req.body);
});
});
I've been reading up on promises and trying to figure out what needs to change here, but I'm having no luck. Can anyone provide any tips?
You need to actually return a value from your function for things to work. Due to having nested Promises you need a couple returns here. One of the core features of promises is if you return a Promise it participates in the calling Promise chain.
So change the following lines
jobID = result['recordset'][0]['JobID'];
to
return result['recordset'][0]['JobID']
and
req.query(`INSERT INTO Jobs (Title, ActiveJD) VALUES ('${title}', 0) ; SELECT ##IDENTITY AS JobID`).then(function (result) {
to
return req.query(`INSERT INTO Jobs (Title, ActiveJD) VALUES ('${title}', 0) ; SELECT ##IDENTITY AS JobID`).then(function (result) {
and
conn.connect().then(function () {
to
return conn.connect().then(function () {
You may need to move code around that is now after the return. You would also be well served moving conn.close() into a single .finally on the end of the connect chain.
I recommend writing a test that you can use to play around with things until you get it right.
const jobId = await addJob(...)
console.log(jobId)
Alternatively rewrite the code to use await instead of .then() calls.

Returning a promise in async function - What is a more elegant / better practice solution?

I am fairly new to async/await and while the following code seems to be working just fine, I have a sense that there must be a much more elegant / best practice way to write this function:
/**
*
* #param {string} email user email address
* #description validates project exists and project owner is userId.
* #returns {Promise} resolves userId if found otherwise null
*/
async getUserFirstAndLastName (userId) {
var userName;
return new Promise(function(resolve, reject){
var params = {
UserPoolId: process.env.userPoolId, /* required */
AttributesToGet: [
'given_name',
'family_name'
/* more items */
],
Filter: `sub = \"${userId}\"`
};
console.log("UserUtils DDB Get User First and Last Name:" + JSON.stringify(params, null,' '));
cognitoidentityserviceprovider.listUsers(params, function(err, data) {
if(err){ // an error occurred
console.log(err, err.stack);
reject(err); // throw error
}
else { // successful response
console.log("Data: " + JSON.stringify(data, null,' '));
if(data.Users.length){
userName = data.Users[0].Attributes[0].Value + " "; // First Name
userName += data.Users[0].Attributes[1].Value; // Last Name
resolve(userName); //return
}
else {
resolve(null); // no user found that matches email
}
}
});
});
}
Seeking guidance...
The issue with your code really seems to be around this line:
cognitoidentityserviceprovider.listUsers(params, function(err, data) {
...
});
This is the old callback style way of handling asynchronous code.
The real way you want to write your code, is for instead of that listUsers function be accepting a callback, it returns a promise. Then you could write your code this way:
async getUserFirstAndLastName (userId) {
const params = {
UserPoolId: process.env.userPoolId, /* required */
AttributesToGet: [
'given_name',
'family_name'
/* more items */
],
Filter: `sub = \"${userId}\"`
};
try {
const data = await cognitoidentityserviceprovider.listUsers(params);
if(data.Users.length){
let userName = data.Users[0].Attributes[0].Value + " "; // First Name
userName += data.Users[0].Attributes[1].Value; // Last Name
return userName;
}
else {
return null; // no user found that matches email
}
}catch(err) {
throw err;
});
});
}
Assuming that you can't just straight rewrite that listUsers function, you could write a function that converts it to a function that returns a promise, like this:
function convertCallbackFnToAsyncFn(fn) {
//Note that we are returning a function
return (...args) => new Promise(function(resolve, reject){
fn(...args, (err, data) => {
if (err) {
reject(err);
}
else {
resolve(data);
}
});
});
}
And fitting this in with the solution above:
try {
//I'm just naming it newFunction here to be really clear about what we're doing
const newFunction = convertCallbackFnToAsyncFn(cognitoidentityserviceprovider.listUsers);
const data = await newFunction(params);
....
Note that I'm using some ES6 spread/rest and fat arrow syntax in my answer.

nodejs mysql doesn't executes query and stops the script

I have a database class with some methods to easily do some queries without formatting the values into a sql query string. So here is my delete method where the script is stopping.
// Other Module
let filter = {
url: 'http://www.example.com/some/route',
},
DB = new MySQL();
DB.delete(filter, 'found_urls').then(result => {
console.log('The row/s are deleted.');
}).catch(error => {
console.log('An error occured.');
console.log(error);
});
// MySQL.js
class MySQL {
// some methods....
delete(filter, table, operator = '=') {
// Parse where clauses and create query string.
let whereCondition = MySQL.parseColumnValue(filter, operator),
sql = `DELETE FROM ${table}`;
// whereCondition is an array: ["url = 'http://www.example.com/some/route'"]
if (whereCondition.length > 1) {
sql += ` WHERE ${whereCondition.join(' AND ')}`;
}
console.log(sql);
// I get "DELETE FROM found_urls WHERE url = 'http://www.example.com/some/route'"
return new Promise((resolve, reject) => {
console.log('You can see me.');
this.database.query(sql, (error, result) => {
console.log('You never see me because the script stopped.');
if (error) {
reject(error);
return;
}
resolve(result);
});
});
}
}
Some days ago it worked perfect but now it stops. I don't know what's wrong with this code.
UPDATE
This class would be used in a webcrawler so I think it's not necessary to protect it for SQL injection.
The table found_urls has about 200 rows, nothing is locked.
Bug I found my problem: A failure in another module. Without this module the crawler is running (with the delete operations).

Log only shows one of four rows of data

I am writing a small Node js application for automatic vehicle location system.
Here is the code for where I am getting trouble.
markerData contains 4 rows but only in the log I can see the last row.
for (var i = 0, len = markerData.length; i < len; i++) {
var thisMarker = markerData[i];
sql.connect(config, function (err) {
var request = new sql.Request();
request.input('myval', sql.Int, thisMarker.id);
request.query('SELECT d.id, d.name, d.lastupdate, p.latitude, p.longitude, p.speed, p.course FROM dbo.devices AS d INNER JOIN dbo.positions AS p ON d.positionid = p.id AND d.id = p.deviceid WHERE (d.id = #myval)', function (err, recordset2) {
if (typeof recordset2 != 'undefined') {
thisMarker.position.lat = recordset2[0].latitude;
thisMarker.position.long = recordset2[0].longitude;
console.log(recordset2[0].id);
}
});
});
}
Please help me to solve the issue.
As var is not a block level variable in terms of scope, when `sql' module takes time to connect to the database asynchronously, the synchronous loop may change the value of the variable that's why you have the last row printed since the variable holds the reference to the last object at the time of successful connection.
Instead of _.each, I would recommend to use async module with async.each since you have few asynchronous operation to get rid of a synchronous loop.
You can check for samples here,
http://justinklemm.com/node-js-async-tutorial/
Here is your updated code with async.each
-> Install async module with npm install async --save
-> Then add the below reference in the required place,
// Reference
var async = require('async');
-> Modified code:
sql.connect(config, function (err) {
if(err) {
console.log('Connection error: ');
console.log(err);
} else {
async.each(markerData, function(thisMarker, callback) {
var request = new sql.Request();
request.input('myval', sql.Int, thisMarker.id);
request.query('SELECT d.id, d.name, d.lastupdate, p.latitude, p.longitude, p.speed, p.course FROM dbo.devices AS d INNER JOIN dbo.positions AS p ON d.positionid = p.id AND d.id = p.deviceid WHERE (d.id = #myval)', function (err, recordset2) {
if(err) {
console.log(err);
callback();
} else {
if (typeof recordset2 != 'undefined') {
thisMarker.position.lat = recordset2[0].latitude;
thisMarker.position.long = recordset2[0].longitude;
console.log(recordset2[0].id);
} else {
console.log('Recordset empty for id: ' + thisMarker.id);
}
callback();
}
});
}, function(err){
if(err) {
console.log(err);
}
});
}
});
I'm not entirely sure how your library works, but presumably recordset2 is an array of records. recordset2[0] is therefore the first record. If you want the next one you should probably try recordset2[1] and so on and so forth.
Arrays: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
You'll probably need to loop through all the elements in the array at some point. use a for loop for that:
for (var i = 0; i < recordset2.length; i++ {
console.log(recordset2[i])
}
That will print out everything your query returns.

Categories