Passing function to Knex select method breaks querying - node.js

The code:
const knex = require("../../db/knex");
module.exports = (request, response) => {
knex
.select((builder) => {
const select = request.query.select;
if (select) {
if (select.constructor === String) {
builder.select(select);
} else if (select.constructor === Array) {
builder.select(...select);
}
} else {
/* Anything that goes here or inside this function, breaks it. */
}
})
.from("tags")
.where((builder) => {
const filter = request.query.filter;
if (filter) {
if (filter.constructor === Object) {
builder.where(filter);
} else if (filter.constructor === Array) {
builder.where(...filter);
}
} else {
builder.clear("where");
}
})
.then((result) => response.json(result))
.catch((error) => response.json({ ...error, message: error.stack }));
};
The full error:
{
"length": 180,
"name": "error",
"severity": "ERROR",
"code": "42601",
"position": "16",
"file": "d:\\pginstaller_13.auto\\postgres.windows-x64\\src\\backend\\parser\\parse_target.c",
"line": "1296",
"routine": "ExpandAllTables",
"message": "error: select (select *) from \"tags\" - SELECT * with no tables specified is not valid\n at Parser.parseErrorMessage (C:\\Users\\Lucas\\Desktop\\Repositories\\projeto-integrador-dh-g1\\node_modules\\pg-protocol\\dist\\parser.js:278:15)\n at Parser.handlePacket (C:\\Users\\Lucas\\Desktop\\Repositories\\projeto-integrador-dh-g1\\node_modules\\pg-protocol\\dist\\parser.js:126:29)\n at Parser.parse (C:\\Users\\Lucas\\Desktop\\Repositories\\projeto-integrador-dh-g1\\node_modules\\pg-protocol\\dist\\parser.js:39:38)\n at Socket.<anonymous> (C:\\Users\\Lucas\\Desktop\\Repositories\\projeto-integrador-dh-g1\\node_modules\\pg-protocol\\dist\\index.js:10:42)\n at Socket.emit (events.js:315:20)\n at addChunk (internal/streams/readable.js:309:12)\n at readableAddChunk (internal/streams/readable.js:284:9)\n at Socket.Readable.push (internal/streams/readable.js:223:10)\n at TCP.onStreamRead (internal/stream_base_commons.js:188:23)"
}
I'm using Express to manage the Node server and Knex to manage the database. In this route, if I remove the function from select, everything works. Tried everything, I can't understand why isn't working. Can anyone help me understand why is it breaking?

The select() method in Knex takes in column names to select from your table. I don't think you can properly pass in a function like you're trying to do. You can do that in the .where() like you have. But I'd start by removing all of the if/else logic from inside of select().
If you need to transform the data you're receiving, do that prior to building a query. Perhaps there might be other query building methods that would help you get the result that you're looking for.

As stated in the comment, what is being done is considered by Knex a sub query. I've found out that the proper way to handle these conditions are by using modify.
I got to something like this now:
const knex = require("../../db/knex");
module.exports = (request, response, next) => {
knex
.from("tags")
.modify((builder) => {
const filter = request.query.filter;
const select = request.query.select;
const order = request.query.order;
const id = request.params.id;
if (select) {
if (select.constructor === String || select.constructor === Object) {
builder.select(select);
} else if (select.constructor === Array) {
builder.select(...select);
}
}
if (id) {
builder.where({ id: id }).first();
} else if (filter) {
if (filter.constructor === Object) {
builder.where(filter);
} else if (filter.constructor === Array) {
builder.where(...filter);
}
}
if (!id) {
if (order) {
if (order.constructor === String) {
builder.orderBy(order);
} else if (order.constructor === Array) {
builder.orderBy(...order);
}
}
}
})
.then((result) => response.json(result))
.catch((error) => next(error));
};

Related

Not translating key on model define unique custom error

Problem
Using 18next.t function to translate key, is getting me the generic sequelize unique constraint error message instead of defined custom message
Environment
sequelize#5.22.4
i18next#21.3.3
Model definition candidate.js
...
module.exports = (sequelize, DataTypes) => {
const Candidate = sequelize.define('Candidate', {
status: {
type: DataTypes.ENUM,
values: [
...
],
},
docTin : {
...
unique: {
args: 'candidates_unique_doctin_company_unity',
get msg() { return i18next.t('invalid-candidate-unique-doc-tin') }
},
...
Result:
docTin must be unique
Expected:
{Custom error message located on lang.json}
The solution that i founded was prototyping the main create function on model definition, to allow use functions on unique msg properties
...
const orgCreate = Candidate.create;
Candidate.create = function(){
return orgCreate
.apply(this, arguments)
.catch(err => {
const uniqueErrorName = 'SequelizeUniqueConstraintError'
if (err.name === uniqueErrorName) {
err.errors = err.errors.map(e => ({
...e,
message: typeof e.message === 'function' ? e.message() : e.message
}))
}
throw err;
});
...
replacing
get msg() { return i18next.t('invalid-candidate-unique-doc-tin') }
for
msg: () => i18next.t('invalid-candidate-unique-doc-tin')

Why Hook is called in all update services methods

I'm create a hook file with the following information, which is Hooks.js
Hooks.js is working to authenticate an actions with JWT when need it, I dont need it in all servies calls.
As my understanding the syntax to call a hook was app/use route/hooks and those hooks were only applied to and specific route and not globally.
module.exports = {
errorHandler: (context) => {
if (context.error) {
context.error.stack = null;
return context;
}
},
isValidToken: (context) => {
const token = context.params.headers.authorization;
const payload = Auth.validateToken(token);
console.log(payload);
if(payload !== "Invalid" && payload !== "No Token Provided"){
context.data = payload._id;
}
else {
throw new errors.NotAuthenticated('Authentication Error Token');
}
},
isValidDomain: (context) => {
if (
config.DOMAINS_WHITE_LIST.includes(
context.params.headers.origin || context.params.headers.host
)
) {
return context;
}
throw new errors.NotAuthenticated("Not Authenticated Domain");
},
normalizedId: (context) => {
context.id = context.id || context.params.route.id;
},
normalizedCode: (context) => {
context.id = context.params.route.code;
},
};
Then I create a file for services and routes, like the following:
const Hooks = require("../../Hooks/Hooks");
const userServices = require("./user.services");
module.exports = (app) => {
app
.use("/users", {
find: userServices.find,
create: userServices.createUser,
})
.hooks({
before: {
find: [Hooks.isValidDomain],
create: [Hooks.isValidDomain],
},
});
app
.use("/users/:code/validate", {
update: userServices.validateCode,
})
.hooks({
before: {
update: [Hooks.isValidDomain, Hooks.normalizedCode],
},
});
app
.use("/users/personal", {
update: userServices.personalInfo,
})
.hooks({
before: {
update: [Hooks.isValidDomain, Hooks.isValidToken],
},
});
};
Why Hooks.isValidToken applies to all my update methods? Even if I'm not calling it?
Please help.
app.hooks registers an application level hook which runs for all services. If you only want it for a specific service and method it needs to be app.service('users').hooks().

NodeJS String.replace() problem while filtering

I can't modify the filtering parameter with
String.replace()
I can get the filtering keys from the URL as an object but its badly fromatted from me.
Filtering: {{URL}}/api/v1/bootcamps?averageCost[lt]=10000
Current format: { averageCost: { lt: '10000' } }
Right fromat: { averageCost: { $lt: '10000' } }
So I tried to convert it as a String and replace that value. But that value can be: lt, lte, gt, gte, in but it has some problem because the line after the .replace() method doesnt executed and of course the catch block cathes the error...
My code snippet:
try {
console.log(req.query);
const queryStr = JSON.stringify(req.query);
console.log(queryStr); //thats the last thing I get
queryStr = queryStr.replace(
/\b(gt|gte|lt|lte|in)\b/g,
match => `$${match}`
);
console.log(queryStr); // I dont get this
const bootcamps = await Bootcamp.find();
res.status(200).json({
succes: true,
count: bootcamps.length,
data: bootcamps
});
} catch (err) {
return res.status(404).json({ succes: false });
}
To replace it correctly you should use something like this:
let queryStr = '{ "averageCost": { "lt": "10000" }, "test": { "gt": "12345"} }';
const regex = /\b(gt|gte|lt|lte|in)\b/g;
queryStr = queryStr.replace(regex, '$$' + "$1"); // <-- here is the correct replace
This will replace queryStr with:
{ "averageCost": { "$lt": "10000" }, "test": { "$gt": "12345"} }
JSFiddle https://jsfiddle.net/c52z8ewr/
If you need the object back just do JSON.parse(queryStr)
You can also Try This
const queryCpy = { ...this.query };
// console.log(queryCpy, 'before filter');
console.log(queryCpy, 'after filter');
let queryString = JSON.stringify(queryCpy);
queryString = queryString.replace(
/\b(gt|gte|lt|lte)\b/g,
(rep) => `$${rep}`,
);
console.log(queryString, 'after filter');
If want an object in return do:
const bootcamps = await Bootcamp.find(JSON.parse(queryStr));

Unable to write item(s) to DynamoDB table utilizing DocumentClient - Nodejs

I'm absolutely brand new to DynamoDb and I'm trying to simply write an object from a NodeJS Lambda. Based on what I've read and researched I should probably be using DocumentClient from the aws-sdk. I also found the following question here regarding issues with DocumentClient, but it doesn't seem to address my specific issue....which I can't really find/pinpoint unfortunately. I've set up a debugger to help with SAM local development, but it appears to be only providing some of the errors.
The code's implementation is shown here.
var params = {
TableName: "March-Madness-Teams",
Item: {
"Id": {"S": randstring.generate(9)},
"School":{"S": team_name},
"Seed": {"S": seed},
"ESPN_Id": {"S": espn_id}
}
}
console.log(JSON.stringify(params))
dynamodb.put(params, (error,data) => {
if (error) {
console.log("Error ", error)
} else {
console.log("Success! ", data)
}
})
Basically I'm scrubbing a website utilizing cheerio library and cherry picking values from the DOM and saving them into the json object shown below.
{
"TableName": "March-Madness-Teams",
"Item": {
"Id": {
"S": "ED311Oi3N"
},
"School": {
"S": "BAYLOR"
},
"Seed": {
"S": "1"
},
"ESPN_Id": {
"S": "239"
}
}
}
When I attempt to push this json object to Dynamo, I get errors says
Error MultipleValidationErrors: There were 2 validation errors:
* MissingRequiredParameter: Missing required key 'TableName' in params
* MissingRequiredParameter: Missing required key 'Item' in params
The above error is all good in well....I assume it didn't like the fact that I had wrapped those to keys in strings, so I removed the quotes and sent the following
{
TableName: "March-Madness-Teams",
Item: {
"Id": {
"S": "ED311Oi3N"
},
"School": {
"S": "BAYLOR"
},
"Seed": {
"S": "1"
},
"ESPN_Id": {
"S": "239"
}
}
}
However, when I do that...I kind of get nothing.
Here is a larger code snippet.
return new Promise((resolve,reject) => {
axios.get('http://www.espn.com/mens-college-basketball/bracketology')
.then(html => {
const dynamodb = new aws.DynamoDB.DocumentClient()
let $ = cheerio.load(html.data)
$('.region').each(async function(index, element){
var preregion = $(element).children('h3,b').text()
var region = preregion.substr(0, preregion.indexOf('(') - 1)
$(element).find('a').each(async function(index2, element2){
var seed = $(element2).siblings('span.rank').text()
if (seed.length > 2){
seed = $(element2).siblings('span.rank').text().substring(0, 2)
}
var espn_id = $(element2).attr('href').split('/').slice(-2)[0]
var team_name = $(element2).text()
var params = {
TableName: "March-Madness-Teams",
Item: {
"Id": randstring.generate(9),
"School":team_name,
"Seed": seed,
"ESPN_Id": espn_id
}
}
console.log(JSON.stringify(params))
// dynamodb.put(params)
// .then(function(data) {
// console.log(`Success`, data)
// })
})
})
})
})
Can you try without the type?
Instead of
"School":{"S": team_name},
for example, use
"School": team_name,
From your code, I can see the mis promise on the dynamodb request. Try to change your lines :
dynamodb.put(params).then(function(data) {
console.log(`Success`, data)
})
to be :
dynamodb.put(params).promise().then(function(data) {
console.log(`Success`, data)
})
you can combine with await too :
await dynamodb.put(params).promise().then(function(data) {
console.log(`Success`, data)
})
exports.lambdaHandler = async (event, context) => {
const html = await axios.get('http://www.espn.com/mens-college-basketball/bracketology')
let $ = cheerio.load(html.data)
const schools = buildCompleteSchoolObject(html, $)
try {
await writeSchoolsToDynamo(schools)
return { statusCode: 200 }
} catch (error) {
return { statusCode: 400, message: error.message }
}
}
const writeSchoolsToDynamo = async (schools) => {
const promises = schools.map(async school => {
await dynamodb.put(school).promise()
})
await Promise.all(promises)
}
const buildCompleteSchoolObject = (html, $) => {
const schools = []
$('.region').each(loopThroughSubRegions(schools, $))
return schools
}
const loopThroughSubRegions = (schools, $) => {
return (index, element) => {
var preregion = $(element).children('h3,b').text()
var region = preregion.substr(0, preregion.indexOf('(') - 1)
$(element).find('a').each(populateSchoolObjects(schools, $))
}
}
const populateSchoolObjects = (schools, $) => {
return (index, element) => {
var seed = $(element).siblings('span.rank').text()
if (seed.length > 2) {
seed = $(element).siblings('span.rank').text().substring(0, 2)
}
var espn_id = $(element).attr('href').split('/').slice(-2)[0]
var team_name = $(element).text()
schools.push({
TableName: "March-Madness-Teams",
Item: {
"Id": randstring.generate(9),
"School": team_name,
"Seed": seed,
"ESPN_Id": espn_id
}
})
}
}
I know this is drastically different from what I started with but I did some more digging and kind of kind of worked to this...I'm not sure if this is the best way, but I seemed to get it to work...Let me know if something should change!
Oh I understand what you want.
Maybe you can see the code above works, but there is one concept you have to improve here about async - await and promise especially on lambda function.
I have some notes here from your code above, maybe can be your consideration to improve your lambda :
Using await for every promise in lambda is not the best approach because we know the lambda time limitation. But sometimes we can do that for other case.
Maybe you can change the dynamodb.put method to be dynamodb.batchWriteItem :
The BatchWriteItem operation puts or deletes multiple items in one or more tables.
Or If you have to use dynamodb.put instead, try to get improve the code to be like so :
const writeSchoolsToDynamo = async (schools) => {
const promises = schools.map(school => {
dynamodb.put(school).promise()
})
return Promise.all(promises)
}

GraphQL Resolver returns error "Cannot read forEach of Undefined"

I have a graphql endpoint that I'm running a query against, and I'm building my resolver that is massaging the data before returning to the client. My query is this:
query getTransactions($transID: String!, $confidence: Float) {
transactions(parentID: $transID, confidence: $confidence) {
id
childrens {
id
name
email
phone
age
connectionInfo {
type
confidence
}
}
name
email
phone
age
}
}
and my resolver is currently looking like this:
const getTransactions = (args: any): any => {
const { parentID, confidence } = args;
const trxs = transactions.filter(t => {
return t.id === parentID;
});
let finalChildrens: any[] = [];
trxs.forEach(t => {
finalChildrens.concat(filteredChildren(t));
});
trxs.concat(finalChildrens);
return trxs;
};
const filteredChildren = (t: any): any[] => {
log.debug({ typeCheck: typeof t.childrens, children: t.childrens });
let outputChildren: any[] = [];
if (typeof t.childrens !== undefined) {
t.childrens.forEach((c1: any) => {
if (typeof c1.childrens !== undefined) {
outputChildren.concat(filteredChildren(c1));
outputChildren.push(c1);
} else {
outputChildren.push(c1);
}
});
return outputChildren;
} else {
return ['no child'] as any[];
}
};
The issue I'm facing is that I'm continually getting this error either in the client or graphiql is this:
"Cannot read property 'forEach' of undefined"
I want to say that it has to do with either the forEach in filteredChildren or inside the resolver itself. I'm going through these "gymnastics" in order to get a flat array that is retrieved recursively from the underlying data. How would someone check the array to see if it's filled or not? (or in this case, if the array exists at all?)
The condition typeof t.childrens !== undefined is always true. You should either use typeof t.childrens !== "undefined" or t.childrens !== undefined.

Resources