I need to query my database for users based on an array of emails and then execute a function for each result, I do this with eachAsync:
mongoose.model('User')
.find({email: {$in: ['foo#bar.com', 'bar#foo.com']}})
/* -- Run side effects before continuing -- */
.cursor()
.eachAsync((doc) => {
// do stuff
});
The problem I'm having is that I need to return a 404 status if any of the users with the given emails do not exist.
I've been looking through the mongoose docs but I can't seem to find a way of running "side effects" when working with queries. Simply "resolving" the DocumentQuery with .then doesn't work since you can't turn it into a cursor afterwards.
How can I achieve this?
You could try implementing it as shown below. I hope it helps.
// Function using async/await
getCursor: async (_, res) => {
try {
const result = []; // To hold result of cursor
const searchArray = ['foo#bar.com', 'bar#foo.com'];
let hasError = false; // to track error when email from find isn't in the array
const cursor = await mongoose.model('User').find({ email: { $in: searchArray } }).cursor();
// NOTE: Use cursor.on('data') to read the stream of data passed
cursor.on('data', (cursorChunk) => {
// NOTE: Run your side effect before continuing
if (searchArray.indexOf(cursorChunk.email) === -1) {
hasError = true;
res.status(404).json({ message: 'Resource not found!' });
} else {
// Note: Push chunk to result array if you need it
result.push(cursorChunk);
}
});
// NOTE: listen to the cursor.on('end')
cursor.on('end', () => {
// Do stuff or return result to client
if (!hasError) {
res.status(200).json({ result, success: true });
}
});
} catch (error) {
// Do error log and/or return to client
res.status(404).json({ error, message: 'Resource not found!' });
}
}
Related
Codecademy video: link
Explanation:
As part of my Codecademy Back-End Engineer training, I have to do a project outside of their platform. The goal of this project is to make sure a node application is protected from common web attacks.
One challenge I faced was securing the code from Cross-Site Scripting (XSS) attacks. To do this, I used a package called express-validator#6.12.1. The code uses a function called validator.escape which is supposed to protect against any malicious code being inserted into an input form. However, I am getting an error in the console when I try to use it.
Terminal output :
TypeError: validator.escape is not a function
Here is the code :
const validator = require("express-validator");
app.post("/public_forum", function (request, response) {
if (request.session.loggedin) {
var comment = validator.escape(request.body.comment);
var username = request.session.username;
if (comment) {
db.all(
`INSERT INTO public_forum (username,message) VALUES ('${username}','${comment}')`,
(err, rows) => {
console.log(err);
}
);
db.all(`SELECT username,message FROM public_forum`, (err, rows) => {
console.log(rows);
console.log(err);
response.render("forum", { rows });
});
} else {
db.all(`SELECT username,message FROM public_forum`, (err, rows) => {
console.log(rows);
console.log(err);
response.render("forum", { rows });
});
}
comment = "";
} else {
response.redirect("/");
}
comment = "";
//response.end();
});
In the video of Codecademy, the guy uses this function.
Try with:
const {check, validationResult} = require('express-validator');
app.post('/public_forum', async function (request, response) {
if (request.session.loggedin) {
await check('comment').trim().escape().run(req);
const validationResult = await validationResult(req);
if (validationResult.isEmpty()) {
// Good to go...
const { comment } = req.body;
}
...
Link to official docs
I have implemented your code. I tried to add both a malicious and safe comment, but I got an error message on my browser that said, "Port 4000 Not Found." Every time I run the code, it kills the port. So I have implemented another code that works well based on what you sent me.
// This code defines a post request handler for the "/public_forum" endpoint.
app.post('/public_forum', async function (request, response) {
// Check if the user is logged in by checking the session data.
if (request.session.loggedin) {
// Trim and escape the incoming comment.
await check('comment').trim().escape().run(request);
// Get the validation result of the incoming comment.
const errors = validationResult(request);
// If the validation result contains errors, return a 400 status with the errors in a JSON format.
if (!errors.isEmpty()) {
return response.status(400).json({ errors: errors.array() });
}
// Get the comment from the request body.
const { comment } = request.body;
// If a valid comment exists, insert it into the "public_forum" database table.
if (comment) {
db.run(
`INSERT INTO public_forum (username,message) VALUES (?,?)`, [request.session.username, comment],
(err) => {
// If an error occurs while inserting the comment, log the error.
if (err) {
console.error(err);
}
}
);
}
// Select all the rows from the "public_forum" table.
db.all(`SELECT username,message FROM public_forum`, (err, rows) => {
// If an error occurs while selecting the rows, log the error.
if (err) {
console.error(err);
}
// Log the selected rows.
console.log(rows);
// Render the "forum" template, passing in the selected rows as a parameter.
response.render("forum", { rows });
});
} else {
// If the user is not logged in, redirect them to the homepage.
response.redirect("/");
}
});
Hello I am new to Postgresql and I wanted to learn how one handles 0 results as an error is thrown. Essentially I want to get a user if it doesn't exist, return null if one doesn't, and have an error handler. Below is the current code I am using. Any tips on a better way to do this are appreciated!
var options = {
// Initialization Options
promiseLib: promise
};
var pgp = require('pg-promise')(options);
var connectionString = 'postgres://localhost:5432/myDbName';
var db = pgp(connectionString);
function getUser(id) {
let user = new Promise(function(resolve, reject) {
try {
db.one('select * from users where loginName = $1', id).then(function(data) {
console.log(data);
resolve(data);
}).catch (function (e) {
console.log('error: '+e);
reject(e);
});
}
catch (e) {
console.log('error: '+e);
reject(e);
}
});
return user;
}
output in console:
error: QueryResultError {
code: queryResultErrorCode.noData
message: "No data returned from the query."
received: 0
query: "select * from users where loginName = 'someUserName'"
}
I am the author of pg-promise.
In the realm of promises one uses .then to handle all normal situations and .catch to handle all error situations.
Translated into pg-promise, which adheres to that rule, you execute a database method that resolves with results that represent all the normal situations, so anything else ends up in .catch.
Case in point, if returning one or no rows is a normal situation for your query, you should be using method oneOrNone. It is only when returning no row is an invalid situation you would use method one.
As per the API, method oneOrNone resolves with the data row found, or with null when no row found, which you can check then:
db.oneOrNone('select * from users where loginName = $1', id)
.then(user=> {
if (user) {
// user found
} else {
// user not found
}
})
.catch(error=> {
// something went wrong;
});
If, however, you have a query for which returning no data does represent an error, the proper way of checking for returning no rows would be like this:
var QRE = pgp.errors.QueryResultError;
var qrec = pgp.errors.queryResultErrorCode;
db.one('select * from users where loginName = $1', id)
.then(user=> {
// normal situation;
})
.catch(error=> {
if (error instanceof QRE && error.code === qrec.noData) {
// found no row
} else {
// something else is wrong;
}
});
Similar considerations are made when choosing method many vs manyOrNone (method any is a shorter alias for manyOrNone).
Type QueryResultError has a very friendly console output, just like all other types in the library, to give you a good idea of how to handle the situation.
In your catch handler for the query, just test for that error. Looking at pg-promise source code, a code of noData is 0. So just do something like this:
db.one('select * from users where loginName = $1', id).then(function(data) {
console.log(data);
resolve(data);
}).catch (function (e) {
if(e.code === 0){
resolve(null);
}
console.log('error: '+e);
reject(e);
});
I would like to know if it's possible to run a series of SQL statements and have them all committed in a single transaction.
The scenario I am looking at is where an array has a series of values that I wish to insert into a table, not individually but as a unit.
I was looking at the following item which provides a framework for transactions in node using pg. The individual transactions appear to be nested within one another so I am unsure of how this would work with an array containing a variable number of elements.
https://github.com/brianc/node-postgres/wiki/Transactions
var pg = require('pg');
var rollback = function(client, done) {
client.query('ROLLBACK', function(err) {
//if there was a problem rolling back the query
//something is seriously messed up. Return the error
//to the done function to close & remove this client from
//the pool. If you leave a client in the pool with an unaborted
//transaction weird, hard to diagnose problems might happen.
return done(err);
});
};
pg.connect(function(err, client, done) {
if(err) throw err;
client.query('BEGIN', function(err) {
if(err) return rollback(client, done);
//as long as we do not call the `done` callback we can do
//whatever we want...the client is ours until we call `done`
//on the flip side, if you do call `done` before either COMMIT or ROLLBACK
//what you are doing is returning a client back to the pool while it
//is in the middle of a transaction.
//Returning a client while its in the middle of a transaction
//will lead to weird & hard to diagnose errors.
process.nextTick(function() {
var text = 'INSERT INTO account(money) VALUES($1) WHERE id = $2';
client.query(text, [100, 1], function(err) {
if(err) return rollback(client, done);
client.query(text, [-100, 2], function(err) {
if(err) return rollback(client, done);
client.query('COMMIT', done);
});
});
});
});
});
My array logic is:
banking.forEach(function(batch){
client.query(text, [batch.amount, batch.id], function(err, result);
}
pg-promise offers a very flexible support for transactions. See Transactions.
It also supports partial nested transactions, aka savepoints.
The library implements transactions automatically, which is what should be used these days, because too many things can go wrong, if you try organizing a transaction manually as you do in your example.
See a related question: Optional INSERT statement in a transaction
Here's a simple TypeScript solution to avoid pg-promise
import { PoolClient } from "pg"
import { pool } from "../database"
const tx = async (callback: (client: PoolClient) => void) => {
const client = await pool.connect();
try {
await client.query('BEGIN')
try {
await callback(client)
await client.query('COMMIT')
} catch (e) {
await client.query('ROLLBACK')
}
} finally {
client.release()
}
}
export { tx }
Usage:
...
let result;
await tx(async client => {
const { rows } = await client.query<{ cnt: string }>('SELECT COUNT(*) AS cnt FROM users WHERE username = $1', [username]);
result = parseInt(rows[0].cnt) > 0;
});
return result;
Ok, lets say I have two Models. Contract and CommLog. Both work find independently but I need many CommLog to relate to each Contract.
In the ContractSchema trying async
ContractSchema.methods.getCommLog = function getCommLog(){
var log = false;
async.parallel([
function(){
CommLog.find({commType:'contract',parent:this._id},function(err,comms){
log = comms;
});
}],
function(){return log;});
};
Where I am trying to use it
router.get('/:code', function(req, res, next) {
Contract.findOne({accessCode:req.params.code},function(err,contract){
if(err)
res.send(err);
var data;
if(contract != null){
var comms = contract.getCommLog();
data = {error:false,data:contract,commlog:comms}
}else{
data = {error:true,message:"No Contract"}
}
res.json(data);
});
});
Where it shows var comms = contract.getCommLog(); It is never returning anything because the getCommLog() is not executing async...
I think its my misunderstanding of mongoose querying, so if you understand what I am trying to accomplish, please let me know what I am doing wrong. I have tried without async which would always return false.
The find call can return all matching results with one query, so I don't think you need async here. The reason it is not populating correctly when you call res.json(data) is because you are not waiting for the method call to finish before you fire off your server response. You would be better off nesting an additional CommLogs.find call within the Contract.find call, and only sending your response once that finishes.
//pseudo code:
Contract.find({}, function(err, contract) {
if(err || !contract) {
//return error response
}
else {
CommLogs.find({contract: contract._id}, function(err, commlogs) {
if(err || !commlogs) {
//return error response 2
}
else {
res.json({errors: false, contract: contract, commlogs: commlogs});
}
});
}
}
Here is my code :
server.get(url_prefix + '/user/:user_id/photos', function(req, res, next) {
if (!req.headers['x-session-id']) {
res.send({
status: {
error: 1,
message: "Session ID not present in request header"
}
})
} else {
User.findOne({
session_id: req.headers['x-session-id']
}, function(err, user) {
if (user) {
var user_id = req.params.user_id
Album.find({userId : user_id})
.populate('images')
.exec(function (err, albums) {
if (albums) {
albums.forEach(function(album, j) {
var album_images = album.images
album_images.forEach(function(image, i) {
Like.findOne({imageID : image._id, userIDs:user._id}, function(err,like){
if(like){
albums[j].images[i].userLike = true;
}
})
})
})
return res.send({
status: {
error: 0,
message: "Successful"
},
data: {
albums: albums
}
})
} else
return notify_error(res, "No Results", 1, 404)
})
}
else {
res.send({
status: {
error: 1,
message: "Invalid Session ID"
}
})
}
})
}
})
I am trying to add a extra value (albums[j].images[i].userLike = true;) to my images array, which is inside album array.
The problem is return res.send({ send the data before we get response from the foreach
How can I make it work, so that return should happen only after foreach has completed all the iteration
You will have to wait with invoking res.send until you fetched all the likes for all the images in each of the albums. E.g.
var pendingImageLikes = album_images.length;
album_images.forEach(function(image, i) {
Like.findOne({imageID : image._id, userIDs:user._id}, function(err,like){
if (like) {
albums[j].images[i].userLike = true;
}
if (!--pendingImageLikes) {
// we fetched all likes
res.send(
// ...
);
}
});
You might need to special case for album_images.length === 0.
Also, this does not take into account that you have multiple albums with multiple images each. You would have to delay res.send there in a very similar way to make this actually work. You might want to consider using a flow control library like first (or any other of your preference, just search for "flow control library") to make this a bit easier.
Also, you might want to consider not relying on semicolon insertion and manually type your semicolons. It prevents ambiguous expressions and makes the code easier to read.
Since you need your code to wait until all of the find operations have completed, I'd suggest you consider using the async package, and specifically something like each (reference). It makes using async loops cleaner, especially when dealing with MongoDB documents and queries. There are lots of nice features, including the ability to sequentially perform a series of functions or waterfall (when you want to perform a series, but pass the results from step to step).
> npm install async
Add to your module:
var async = require("async");
Your code would look something like this:
albums.forEach(function(album, j) {
async.each(album.images, function(album, done) {
Like.findOne({imageID: image._id, userIDs:user._id}, function(err, like){
if(!err && like){
albums[j].images[i].userLike = true;
}
done(err); // callback that this one has finished
})
})
}, function (err) { // called when all iterations have called done()
if (!err) {
return res.send({
status: {
error: 0,
message: "Successful"
},
data: {
albums: albums
}
});
}
return notify_error(res, "No Results", 1, 404);
});
});