I'm relatively new to node and socketio. What I'm trying to achieve is authenticate users using passport and send total number logged user count to the view. Such that if a logged in user can see the total number of current-logged-in users and if a user logs that the count decreases or increases when someone else log-ins respectively.
Using passport.socketio to access authenticated passport user information from a socket.io connection.
In the callback, I'm storing the username in a mongoose collection, and accordingly on the logout removing the user from the collection. I get a count of the number of users in the model which I need to pass and bind to the view. Jade being the template engine. Below is how my onAuthorizeSuccess callback looks like where I try to pass the count to home.jade.
function onAuthorizeSuccess(data, accept) {
var username = data.user.username;
var User = mongoose.model('loggedusers', userSchema);
var user = new User({
username: username
});
user.save(function (err, data) {
if (err) console.log(err);
else {
console.log('Saved : ', data);
}
User.count({}, function (err, c) {
console.log('Count is ' + c);
app.get('/', function (req, res) {
res.render('home', {
count: {
countData: c
}
});
});
});
});
console.log('successful connection to socket.io ');
accept(); //Let the user through
}
And in the jade view I try to set it using
li Logged Users ---> #{countData.c}
But, countData is undefined in the view.
How should I go about rendering a dynamic value from the server to the view in jade?
Any assistance much appreciated.
Thanks,
Arnab
Your variable is wrong you should use instead #{count} with:
res.render('home', {count: c});
Figured this out.
Made a function to server content over a socket, that the control helps updating on the front-end
module.exports = function (socket) {
setInterval(function () {
var UserSchema = require('mongoose').model('loggedusers');
UserSchema.count({}, function(err, c){
console.log('Count is ' + c);
socket.emit('send:count', {
count: c
});
});
}, 1000);
};
And the angular controller
angular.module('myApp.controllers', []).
controller('AppCtrl', function ($scope, socket) {
socket.on('send:count', function (data) {
$scope.count = data.count;
});
});
and in jade {{count}} and add div.container(ng-controller='AppCtrl') should give the updated count on the front-end.
Arnab
Related
I'm using node and postgres, I'm new to writing async function, what I'm trying to do is a very simple query that will do a total count of records in the database, add one to it and return the result. The result will be visible before the DOM is generated. I don't know how to do this, since async function doesn't return value to callers (also probably I still have the synchronous mindset). Here's the function:
function generateRTA(callback){
var current_year = new Date().getFullYear();
const qry = `SELECT COUNT(date_part('year', updated_on))
FROM recruitment_process
WHERE date_part('year', updated_on) = $1;`
const value = [current_year]
pool.query(qry, value, (err, res) => {
if (err) {
console.log(err.stack)
} else {
var count = parseInt(res.rows[0].count) + 1
var rta_no = String(current_year) + '-' + count
callback(null, rta_no)
}
})
}
For the front-end I'm using pug with simple HTML form.
const rta_no = generateRTA(function (err, res){
if(err){
console.log(err)
}
else{
console.log(res)
}
})
app.get('/new_application', function(req, res){
res.render('new_application', {rta_number: rta_no})
});
I can see the rta_no in console.log but how do I pass it back to the DOM when the value is ready?
Based on the ajax call async response, it will update the div id "div1" when it gets the response from the Node js .
app.js
app.get("/webform", (req, res) => {
res.render("webform", {
title: "Render Web Form"
});
});
app.get("/new_application", (req, res) => {
// Connect to database.
var connection = getMySQLConnection();
connection.connect();
// Do the query to get data.
connection.query('SELECT count(1) as cnt FROM test ', function(err, rows, fields) {
var person;
if (err) {
res.status(500).json({"status_code": 500,"status_message": "internal server error"});
} else {
// Check if the result is found or not
if(rows.length==1) {
res.status(200).json({"count": rows[0].cnt});
} else {
// render not found page
res.status(404).json({"status_code":404, "status_message": "Not found"});
}
}
});
// Close connection
connection.end();
});
webform.pug - Via asynchronous call
html
head
script(src='https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js')
script.
$(document).ready(function(){
$.ajax({url: "/new_application", success: function(result){
$("#div1").html(result.count);
}});
});
body
div
Total count goes here :
#div1
value loading ...
That seems okay, I'm just not sure of this:
The result will be visible before the DOM is generated
This constraint defeats the purpose of async, as your DOM should wait for the returned value to be there. Instead of waiting for it you could just render the page and once the function returns and runs your callback update the value.
Also, perhaps it's worth having a look into promises
I have a big problem.
I want to iterate over collection a result set and for each set i want to find one result.
This looks like this:
router.get('/', function(req, res) {
var floors = [];
var rooms = [];
req.db.collection('floors').find().sort({_id: 1}).forEach(function(floor) {
floors.push(floor);
});
req.db.collection('rooms').find().sort({_id: 1}).forEach(function(room) {
req.db.collection('floors').findOne({_id: new ObjectID(room.floorId)}, function(error, floor) {
room.floor = floor;
rooms.push(room);
});
});
res.render('rooms', { floors: floors, rooms: rooms });
});
The Problem is that the page will be rendered before the iteration is complete.
I tried to use async and promises, but i didn't get it to run.
Basically you have to wait until all your queries are done before sending the rendering result. Unfortunately you don't use promises so this will get a bit messy.
It appears that you are using the native client and according to the docs there is a second callback that gets called when all iterations are done
http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#forEach
router.get('/', function(req, res, next) {
var floors = [];
var rooms = [];
function done(err){
if(err) {
return next(err);
}
res.render('rooms', { floors: floors, rooms: rooms });
}
function getRooms(err){
if(err){
return next(err);
}
req.db.collection('rooms').find().sort({_id: 1}).forEach(function(room) {
// you already have all the floors, no need to hit the db again
floors.find(floor => floor._id === room.floorId); // not sure about this 100% as _id could be an object
}, done);
}
req.db.collection('floors').find().sort({_id: 1}).forEach(function(floor) {
floors.push(floor);
}, getRooms);
});
to be noted that this request will get quite heavy when your db grows.
I am trying to obtain a column name value or multiple column name values but I am not obtaining it. I am pretty new to Nodejs but debugging it is even difficult. Here is my code:
var express = require('express');
var app = express();
var mongodb = require('mongodb');
var MongoClient = mongodb.MongoClient;
MongoClient.connect(url, function (err, db) {
if (err) {
console.log('Unable to connect', err);
} else {
console.log('Connected');
var collection = db.collection('users');
app.get('/db', function (request, response) {
collection.find({username: request.query.username, password: request.query.password}).toArray(function(err, docs) {
docs.forEach(function(doc) {
console.log(doc.toArray());
});
});
});
}
db.close();
});
I am basically authenticating if the username and password entered in db matches to my REST query and if yes then print success or redirect to a new page or something.
For starters, unless you are actually expecting more than one "user" to share the same "username" and "password" then you are better off using .findOne() for the singular response.
Then what is returned is just a plain JavaScript object, so just reference the properties:
collection.findOne(
{
username: request.query.username,
password: request.query.password
},
function(err,doc) {
if (err) throw err;
console.log( doc.username ); // Just the username value
}
);
If course if "nothing" is returned and there is no "error" then you didn't match anything with your criteria, so either "username" or "password" is wrong.
That's as the basic excercise, but really, authentication is a wheel that was built long ago and you would likely be better of implementing an existing module to handle it.
And get rid of db.close(). The functions here are asynchronous and this will execute before anything else completes. You basically never want to call this in real world applications.
I am using express as my webserver for node and everything seems to be working correctly. The only problem I am encoutering is when I load a specific page ('/learn' route) 10 times repeatedly. Once I do this, express seems to stop working, although no error is logged to the console and nothing wrong is displayed on the page. It just keeps waiting for the host in the browser. What is weird is that the problem doesn't occur if I go from the page with the problem to another page, and then back again. I can repeat this as much as I want without error. Here is my route with the problem:
var bcrypt = require('bcrypt');
var pool = require('../database.js').pool;
module.exports = function(app) {
app.get('/learn', function(req, res, next) {
var query = 'SELECT * FROM questions INNER JOIN answers ON questions.questionID = answers.questionID';
pool.getConnection(function(err, connection) {
connection.query(query, function(err, rows) {
if (err) {
throw err;
}
var data = {
name: req.session.name,
problems: rows,
};
res.render('learn.html', data);
});
});
});
app.post('/learn/checkAnswer', function(req, res) {
//get posted form data
var questionID = req.body.questionID;
var selectedAnswer = req.body.selectedAnswer;
//query database
pool.getConnection(function(err, connection) {
var query = connection.query('SELECT correctAnswer FROM questions WHERE questionID = ?', questionID, function(err, rows) {
res.send({
correctAnswer: rows[0].correctAnswer
});
});
});
});
};
I'm not sure if this makes a difference, but I am using handlebars as my rendering engine instead of jade, as well as node-mysql for my database.
10 is the default size of the node-mysql pool. And since you're not ending the connections retrieved with pool.getConnection, the 11th request will wait indefinitely for a free connection.
Easy to fix:
connection.query(query, function(err, rows) {
connection.end(); // end the connection as soon as possible,
// so it's returned to the pool and can be reused.
if (err) ...
});
given the async nature of mongoose (or sequelize, or redis) queries, what do you do when you have multiple queries you need to make before rendering the view?
For instance, you have a user_id in a session, and want to retrieve some info about that particular user via findOne. But you also want to display a list of recently logged in users.
exports.index = function (req, res) {
var current_user = null
Player.find({last_logged_in : today()}).exec(function(err, players) {
if (err) return res.render('500');
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
current_user = player
}
})
}
// here, current_user isn't populated until the callback fires
res.render('game/index', { title: 'Battle!',
players: players,
game_is_full: (players.length >= 6),
current_user: current_user
});
});
};
So res.render is in the first query callback, fine. But what about waiting on the response from findOne to see if we know this user? It is only called conditionally, so I can't put render inside the inner callback, unless I duplicate it for either condition. Not pretty.
I can think of some workarounds -
make it really async and use AJAX on the client side to get the current user's profile. But this seems like more work than it's worth.
use Q and promises to wait on the resolution of the findOne query before rendering. But in a way, this would be like forcing blocking to make the response wait on my operation. Doesn't seem right.
use a middleware function to get the current user info. This seems cleaner, makes the query reusable. However I'm not sure how to go about it or if it would still manifest the same problem.
Of course, in a more extreme case, if you have a dozen queries to make, things might get ugly. So, what is the usual pattern given this type of requirement?
Yep, this is a particularly annoying case in async code. What you can do is to put the code you'd have to duplicate into a local function to keep it DRY:
exports.index = function (req, res) {
var current_user = null
Player.find({last_logged_in : today()}).exec(function(err, players) {
if (err) return res.render('500');
function render() {
res.render('game/index', { title: 'Battle!',
players: players,
game_is_full: (players.length >= 6),
current_user: current_user
});
}
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
current_user = player
}
render();
})
} else {
render();
}
});
};
However, looking at what you're doing here, you'll probably need to look up the current player information in multiple request handlers, so in that case you're better off using middleware.
Something like:
exports.loadUser = function (req, res, next) {
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
req.player = player
}
next();
})
} else {
next();
}
}
Then you'd configure your routes to call loadUser wherever you need req.player populated and the route handler can just pull the player details right from there.
router.get("/",function(req,res){
var locals = {};
var userId = req.params.userId;
async.parallel([
//Load user Data
function(callback) {
mongoOp.User.find({},function(err,user){
if (err) return callback(err);
locals.user = user;
callback();
});
},
//Load posts Data
function(callback) {
mongoOp.Post.find({},function(err,posts){
if (err) return callback(err);
locals.posts = posts;
callback();
});
}
], function(err) { //This function gets called after the two tasks have called their "task callbacks"
if (err) return next(err); //If an error occurred, we let express handle it by calling the `next` function
//Here `locals` will be an object with `user` and `posts` keys
//Example: `locals = {user: ..., posts: [...]}`
res.render('index.ejs', {userdata: locals.user,postdata: locals.posts})
});
Nowadays you can use app.param in ExpressJS to easily establish middleware that loads needed data based on the name of parameters in the request URL.
http://expressjs.com/4x/api.html#app.param