Node.JS Oracle Patch Request not dynamic - sql

I'm trying to dynamically make a patch request for oracle tables through Node.JS
Here's my setup:
In my router.js file I have this:
const express = require('express');
const router = new express.Router();
const employees = require('../controllers/employees.js');
const smiCats = require('../controllers/smi/smiCats.js');
const auth = require('../controllers/auth.js');
router.route('/login/:id?')
.post(auth.getToken);
router.route('/ams/:id?')
.get(auth.verifyToken, employees.get)
.post(auth.verifyToken, employees.post)
.put(auth.verifyToken, employees.put)
.delete(auth.verifyToken, employees.delete)
.patch(auth.verifyToken, employees.patch);
router.route('/smi/cats/:id?')
.get(auth.verifyToken, smiCats.get)
.post(auth.verifyToken, smiCats.post)
.put(auth.verifyToken, smiCats.put)
.patch(auth.verifyToken, smiCats.patch);
module.exports = router;
That then calls my controller that has my patch function & gets sanitized.
//sanitizer
function sanitizeCats(req) {
const cats = {
cat_desc: req.body.cat_desc,
msg_for: req.body.msg_for,
msg_user_owner: req.body.msg_user_owner || 0,
msg_realtor_owner: req.body.msg_realtor_owner || 0
};
return cats;
}
async function patch(req, res, next) {
try {
let category = sanitizeCats(req);
category.cat_id = parseInt(req.params.id, 10);
const success = await smiCats.patch(category);
if (success) {
res.status(204).end();
} else {
res.status(404).end();
}
} catch (err) {
next(err);
}
}
module.exports.patch = patch;
When that gets executed it calls my db_api module, which assembles the sql statement
(THE NEXT CODE SECTION IS WHERE MY QUESTION COMES FROM)
const database = require('../../services/database.js');
const oracledb = require('oracledb');
const patchSql =
`BEGIN
DECLARE
BEGIN
IF nvl(:cat_desc,'zzz') != 'zzz' THEN
UPDATE smi_contact_cats
SET cat_desc = :cat_desc
WHERE cat_id = :cat_id;
END IF;
IF nvl(:msg_for,'zzz') != 'zzz' THEN
UPDATE smi_contact_cats
SET msg_for = :msg_for
WHERE cat_id = :cat_id;
END IF;
IF nvl(:msg_user_owner,-1) > -1 THEN
UPDATE smi_contact_cats
SET msg_user_owner = :msg_user_owner
WHERE cat_id = :cat_id;
END IF;
IF nvl(:msg_realtor_owner,-1) > -1 THEN
UPDATE smi_contact_cats
SET msg_realtor_owner = :msg_realtor_owner
WHERE cat_id = :cat_id;
END IF;
:rowcount := sql%rowcount;
END;
END;`;
async function patch(cats) {
const category = Object.assign({}, cats);
//add binds
category.rowcount = {
dir: oracledb.BIND_OUT,
type: oracledb.NUMBER
};
const result = await database.simpleExecute(patchSql, category);
return result.outBinds.rowcount === 1;
}
module.exports.patch = patch;
This then calls the database function to actually execute & assemble the sql with the bind variables:
const oracledb = require('oracledb');
const dbConfig = require('../config/database.js');
async function initialize() {
const pool = await oracledb.createPool(dbConfig.beta);
}
module.exports.initialize = initialize;
async function close() {
await oracledb.getPool().close();
}
module.exports.close = close;
function simpleExecute(statement, binds = [], opts = {}) {
return new Promise(async (resolve, reject) => {
let conn;
opts.outFormat = oracledb.OBJECT;
opts.autoCommit = true;
try {
conn = await oracledb.getConnection();
const result = await conn.execute(statement, binds, opts);
resolve(result);
} catch (err) {
reject(err);
} finally {
if (conn) { // conn assignment worked, need to close
try {
await conn.close();
} catch (err) {
console.log(err);
}
}
}
});
}
module.exports.simpleExecute = simpleExecute;
So all of this works... but it's not dynamic enough for me to build our company api. How do I make a more dynamic patch request in Node.JS without having to type out every single column & put an nvl around it to check if it's there. As a side not if there's a better way to dynamically sanitize as well, I'm all ears, but the main question is on how to dynamically build the patch request better.

The current code is suboptimal in that is does one update per property. Here's a more dynamic solution...
Given the following:
create table smi_contact_cats (
cat_id number,
cat_desc varchar2(50),
msg_for varchar2(50),
msg_user_owner varchar2(50),
msg_realtor_owner varchar2(50)
);
insert into smi_contact_cats (
cat_id,
cat_desc,
msg_for,
msg_user_owner,
msg_realtor_owner
) values (
1,
'cat_desc orginal value',
'msg_for orginal value',
'msg_user_owner orginal value',
'msg_realtor_owner orginal value'
);
commit;
You can use logic like this. updatableColumns is the whitelist of columns that can be updated. Note that you can comment and uncomment some of the lines toward the bottom to test various input.
const oracledb = require('oracledb');
const config = require('./db-config.js');
async function patch(cat) {
let conn;
try {
const category = Object.assign({}, cat);
const categoryProps = Object.getOwnPropertyNames(category);
const updatableColumns = ['cat_desc', 'msg_for', 'msg_user_owner'];
// Validate that the pk was passed in
if (!categoryProps.includes('cat_id')) {
throw new Error('cat_id is required');
}
// Now remove the pk col from categoryProps
categoryProps.splice(categoryProps.indexOf('cat_id'), 1);
if (categoryProps.length === 0) {
throw new Error('At least one property must be specified');
}
let sql = 'update smi_contact_cats\nset ';
for (let propIdx = 0; propIdx < categoryProps.length; propIdx++) {
// Here's the whitelist check
if (!updatableColumns.includes(categoryProps[propIdx])) {
throw new Error('Invalid "update" column');
} else {
if (propIdx > 0 && propIdx < categoryProps.length) {
sql += ',\n ';
}
sql += categoryProps[propIdx] + ' = :' + categoryProps[propIdx];
}
}
sql += '\nwhere cat_id = :cat_id';
console.log('here is the sql', sql);
conn = await oracledb.getConnection(config);
const result = await conn.execute(
sql,
category,
{
autoCommit: true
}
);
if (result.rowsAffected && result.rowsAffected === 1) {
return category;
} else {
return null;
}
} catch (err) {
console.error(err);
} finally {
if (conn) {
try {
await conn.close();
} catch (err) {
console.error(err);
}
}
}
}
const patchObj = {
cat_id: 1
};
// Comment and uncomment the following to see various dynamic statements
patchObj.cat_desc = 'cat_desc value';
patchObj.msg_for = 'msg_for value';
patchObj.msg_user_owner = 'msg_user_owner value';
// Uncomment the following line to add a column that's not whitelisted
//patchObj.msg_realtor_owner = 'msg_realtor_owner value';
patch(patchObj)
.then(function(cat) {
console.log('Updated succeeded', cat);
})
.catch(function(err) {
console.log(err);
});

Related

Cloudflare ESI worker / TypeError: Body has already been used

I'm trying to use a CloudFlare worker to manage my backend ESI fragments but i get an error:
Uncaught (in promise) TypeError: Body has already been used. It can only be used once. Use tee() first if you need to read it twice.
Uncaught (in response) TypeError: Body has already been used. It can only be used once. Use tee() first if you need to read it twice.
I don't find where the body has already been used
The process is:
get a response with the parts
Transform the body by replacing parts fragments with sub Backend calls (streamTransformBody function)
return the response
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
});
const esiHeaders = {
"user-agent": "cloudflare"
}
async function handleRequest(request) {
// get cookies from the request
if(cookie = request.headers.get("Cookie")) {
esiHeaders["Cookie"] = cookie
console.log(cookie)
}
// Clone the request so that it's no longer immutable
newRequest = new Request(request)
// remove cookie from request
newRequest.headers.delete('Cookie')
// Add header to get <esi>
newRequest.headers.set("Surrogate-Capability", "abc=ESI/1.0")
console.log(newRequest.url);
const response = await fetch(newRequest);
let contentType = response.headers.get('content-type')
if (!contentType || !contentType.startsWith("text/")) {
return response
}
// Clone the response so that it's no longer immutable
const newResponse = new Response(response.body, response);
let { readable, writable } = new TransformStream()
streamTransformBody(newResponse.body, writable)
newResponse.headers.append('x-workers-hello', 'Hello from
Cloudflare Workers');
return newResponse;
}
async function streamTransformBody(readable, writable) {
const startTag = "<".charCodeAt(0);
const endTag = ">".charCodeAt(0);
let reader = readable.getReader();
let writer = writable.getWriter();
let templateChunks = null;
while (true) {
let { done, value } = await reader.read();
if (done) break;
while (value.byteLength > 0) {
if (templateChunks) {
let end = value.indexOf(endTag);
if (end === -1) {
templateChunks.push(value);
break;
} else {
templateChunks.push(value.subarray(0, end));
await writer.write(await translate(templateChunks));
templateChunks = null;
value = value.subarray(end + 1);
}
}
let start = value.indexOf(startTag);
if (start === -1) {
await writer.write(value);
break;
} else {
await writer.write(value.subarray(0, start));
value = value.subarray(start + 1);
templateChunks = [];
}
}
}
await writer.close();
}
async function translate(chunks) {
const decoder = new TextDecoder();
let templateKey = chunks.reduce(
(accumulator, chunk) =>
accumulator + decoder.decode(chunk, { stream: true }),
""
);
templateKey += decoder.decode();
return handleTemplate(new TextEncoder(), templateKey);
}
async function handleTemplate(encoder, templateKey) {
const linkRegex = /(esi:include.*src="(.*?)".*\/)/gm
let result = linkRegex.exec(templateKey);
let esi
if (!result) {
return encoder.encode(`<${templateKey}>`);
}
if (result[2]) {
esi = await subRequests(result[2]);
}
return encoder.encode(
`${esi}`
);
}
async function subRequests(target){
target = esiHost + target
const init = {
method: 'GET',
headers: esiHeaders
}
let response = await fetch(target, init)
if (!response.ok) {
return ''
}
let text = await response.text()
return '<!--esi-->' + text + '<!--/esi-->'
}

Syntax required for conditional variable use to modify SQL query in Node.JS

I've fairly new to NodeJS, and I'm not sure of the best method or syntax to create an MS SQL query with conditional code. Here's what I want to do, with the query greatly simplified, and using some pseudocode:
// #route GET /api/flow/data/references
async function getDataReferences(req, res) {
const { station, type } = req.query
let pool
try {
pool = await sql.connect(config)
const { recordset } = await pool
.request()
.input('station', sql.NVarChar(50), station).query`
SELECT Reference
FROM TABLE
WHERE Status = 'Done' ` +
if(type === 1) {
`AND Station_1 = #station`
} else if(type === 2) {
`AND Station_2 = #station`
} else {
`AND Station_3 = #station`
}
+ `AND Process = 5`
const processedData = recordset.map((item) => item.Reference)
res.json(processedData)
} catch (error) {
console.log(
`ERROR with Station: ${station} with Type: ${type}`,
error.message,
new Date()
)
res.status(500).json({ message: error.message })
} finally {
await pool.close()
}
}
Depending on the value of "type" supplied to the function, I want the query to reference a different DB column.
UPDATE:
So I've found that the following works, although arguably the formatting isn't quite as nice.
// #route GET /api/flow/data/references
async function getDataReferences(req, res) {
const { station, type } = req.query
let station_column
if(type === 1) {
station_column = 'AND Station_1 = #station'
} else if(type === 2) {
station_column = 'AND Station_2 = #station'
} else {
station_column = 'AND Station_3 = #station'
}
let query = `
SELECT Reference
FROM TABLE
WHERE Status = 'Done'
${station_column}
AND Process = 5`
let pool
try {
pool = await sql.connect(config)
const { recordset } = await pool
.request()
.input('station', sql.NVarChar(50), station).query(query)
const processedData = recordset.map((item) => item.Reference)
res.json(processedData)
} catch (error) {
console.log(
`ERROR with Station: ${station} with Type: ${type}`,
error.message,
new Date()
)
res.status(500).json({ message: error.message })
} finally {
await pool.close()
}
}
I tried just using the template literal substitutions directly in the query, but that wouldn't work. (Perhaps for reasons stated here: https://github.com/tediousjs/node-mssql#es6-tagged-template-literals )
If I don't get any better answer, I'll post this as the answer; but would like to know if there's a best practice method for doing this.
Seems like this works, and isn't overly complicated:
// #route GET /api/flow/data/references
async function getDataReferences(req, res) {
const { station, type } = req.query
let station_column
if(type === 1) {
station_column = 'AND Station_1 = #station'
} else if(type === 2) {
station_column = 'AND Station_2 = #station'
} else {
station_column = 'AND Station_3 = #station'
}
let query = `
SELECT Reference
FROM TABLE
WHERE Status = 'Done'
${station_column}
AND Process = 5`
let pool
try {
pool = await sql.connect(config)
const { recordset } = await pool
.request()
.input('station', sql.NVarChar(50), station).query(query)
const processedData = recordset.map((item) => item.Reference)
res.json(processedData)
} catch (error) {
console.log(
`ERROR with Station: ${station} with Type: ${type}`,
error.message,
new Date()
)
res.status(500).json({ message: error.message })
} finally {
await pool.close()
}
}
Essentially, create the full query string in advance, including the parameters to bind, and then pull that whole string in as the query.

Insert session data into postgres database in Nodejs Expess app

I have an Nodejs express function where I am trying to insert data that is stored in the browser session into my postgres database. When I have the insert statement like this, the insert works but the session-stored customer_id isn't inserted and is just left null.
On the line with "var sql = INSERT INTO journal....", the values $1 and $2 are from user input and work correctly.
How can I get value 3 of the customer_id stored in the session to insert correctly? I would appreciate any advice or greater understanding.
app.post("/addJournalEntry", addJournalEntry);
function addJournalEntry(req, res) {
console.log("Posting data");
// var id = req.query.id;
//body is for post, query is for get
const customer_id = req.session.customer_id;
const journal_entry_date = req.body.journal_entry_date;
const journal_entry = req.body.journal_entry;
const params = [journal_entry, journal_entry_date, customer_id];
addEntryFromDataLayer(params, function (error, addEntry) {
console.log("Back From the addEntryFromDataLayer:", addEntry);
if (error || addEntry == null) {
res.status(500).json({
success: false,
data: error
});
}
else {
// res.json(result);
res.status(200).json(addEntry);
}
});
}
function addEntryFromDataLayer(params, callback) {
console.log("addEntryFromDataLayer called with id");
var sql = "INSERT INTO journal (journal_entry, journal_entry_date, customer_id) VALUES($1::text, $2::text, $3)";
// var params = [id];
pool.query(sql, params, function (err, addEntry) {
if (err) {
console.log("error in database connection");
console.log(err);
callback(err, null);
}
console.log("Found DB result:" + JSON.stringify(addEntry));
callback(null, addEntry);
});
}

How to update correctly a table?

I try to update a table.
I do a first request to get all rows then I update this table with same data but passwords are crypted before update.
'use strict';
var sql = require('./db.js');
var crypto = require('crypto');
var Crypt = require('./encryption.js');
//Support object constructor
var Login = function(login){
this.login = login.login;
this.password = login.password;
};
sql.query("SELECT * FROM login WHERE Password IS NOT NULL OR Password != ''", function(err,res) {
if (err) {
console.log(err);
}
else {
console.log(res.length);
for (var i = 0; i < res.length ; i++) {
//console.log(res[i].Password);
//if (res[i].Password != '' ){
sql.query("UPDATE login SET Password = ? where id=?", [Crypt.encrypt(res[i].Password), i], fu
if (err){
console.log(err);
}else {
console.log(res2, 'ok');
}
}) //}
}
}
})
When I do update only some rows are updatted but not all. I want to do it on all rows.
the mistake was in the query. I was using the iterator as id but ids aren't orderded in the database. It was res[i].id instead of i in the query parameter

SQL tedious add array as parameter

I'm running this SQL query with tedious.js using parameters:
var query = "select * from table_name where id in (#ids)";
request = new sql.Request(query, function(err, rowCount) {
if (err) {
}
});
request.on('row', function(columns) {
});
var id = [1, 2, 3];
request.addParameters('ids', TYPES.Int, id);
connection.execSql(request);
because I am looking for items that matches the ID provided with where ... in ... clause, I need to pass in an array. However, there is no TYPES.Array. How do I this properly?
for this query, i think you'll just have to manually build the entire sql string. the TYPES enum values are for the datatypes in the database, not in your JavaScript code.
//you can like this:
var userIds = result.map(function (el) {
return el.UserId;
}).join(',');
var params = [{
name: 'userIds',
type: TYPES.VarChar,
value: userIds,
options: null}];
var querySql = ['SELECT COUNT([MomentId]) FROM [T_Moment]',
'WHERE [RecordStatus] = ', sysConst.recordStatus.activation, " AND CHARINDEX(','+RTRIM([UserId])+',' , ','+ #userIds +',')>0 "].join(' ');
dbHelper.count(querySql, params, function (err, result) {
if (err) {
callback('error--');
} else {
callback(null, result);
}
});
Try creating the in clause parameters for query dynamically.
// create connection
let ids = [1, 2, 3];
let inClauseParamters = createInClauseParameters();
let query = `select * from table_name where id in (${inClauseParamters})`;
let request = new Request(query, (err, rowCount) => {
if (err) { /* handle error */ }
});
request.on('row', (columns) => { /* get row */});
request = addRequestParameters(ids, request);
connection.execSql(request);
function createInClauseParameters(values) {
return values.map((val, index) => `#Value${index}`).join(',');
}
function addRequestParameters(values, request) {
values.forEach((val, index) => {
request.addParameter(`Value${index}`, TYPES.VarChar, val);
});
return request;
}