Skip to content

Instantly share code, notes, and snippets.

@jbrooksuk
Created June 25, 2013 16:10
var EventEmitter = require('events').EventEmitter,
util = require('util'),
_ = require('underscore'),
crypto = require('crypto');
var Setup = require('./setup');
var mysqlPool = Setup.mysqlPool;
// These are added to account.prefs if they don't exist. This means that when the user gets updated, these will go with it.
// There hidden up here because we don't want to cause confusion.
var PREF_DEFAULTS = {
condenseTable: false,
stickyFooter: true,
pageRowLiimit: 30,
openSearch: false,
comicSans: false,
darkMode: false,
antipodean: false
};
function User() {
EventEmitter.call(this);
var self = this;
this.loggedIn = false;
this.account = {};
this.account.prefs = {};
return this;
};
util.inherits(User, EventEmitter);
var noop = function() { /* */ };
User.prototype.fetch = function(agent_id) {
EventEmitter.call(this);
var self = this;
if(typeof agent_id === 'undefined') throw Error("User#fetch requires an agent id.");
mysqlPool.getConnection(function(err, db) {
db.query("SELECT * FROM _login_ WHERE agent_id = ?", [agent_id], function(err, rows) {
if(err) {
db.end();
throw err;
}
var tmpAcc = {};
if(rows.length > 0) {
tmpAcc = rows[0];
tmpAcc.picture = self.accountPicture(rows[0].agent_name);
tmpAcc.prefs = _.defaults(JSON.parse(tmpAcc.prefs), PREF_DEFAULTS);
self.emit('fetched', tmpAcc);
}else{
self.emit('fetched', false);
}
db.end();
});
})
return this;
};
/**
* This function takes over from autoLogin and manualLogin, requiring
* oAcc to be an object containing the relevant information.
*/
User.prototype.login = function(oAcc, callback) {
EventEmitter.call(this);
var self = this;
callback = callback || noop;
if(typeof oAcc !== 'object') throw Error("User#login requires first parameter to be an object.");
// If the user is manually logging in we need to encode some shiz.
if(oAcc.manual) {
var md5sum = crypto.createHash('md5');
md5sum.update(oAcc.password);
oAcc.password = md5sum.digest('hex'); // Override, we need it.
}
mysqlPool.getConnection(function(err, db) {
if(err) {
db.end();
throw err;
}
db.query("SELECT agent_id FROM _login_ WHERE user = ? AND pass = ?", [oAcc.username, oAcc.password], function(err, rows) {
if(err) {
db.end();
throw err;
}
if(rows.length > 0) {
// This may seem backwards, but we now call fetch which returns the data.
self.fetch(rows[0].agent_id);
self.once('fetched', function(account) {
if(account) {
self.loggedIn = true;
self.account = account; // Already formmatted. Boosh!
self.emit('logged in', account);
callback(err, account);
}else{
self.emit('logged in', false);
callback(err, false);
}
self.removeAllListeners('fetched');
db.end();
});
}else{
self.emit('logged in', false);
callback(err, false);
}
db.end();
});
});
return this;
};
User.prototype.logout = function(callback) {
EventEmitter.call(this);
callback = callback || noop;
this.set('status', 0, true);
this.account = {};
this.loggedIn = false;
this.emit('logged out');
callback();
return this;
};
User.prototype.accountPicture = function(agent_name) {
this.account.picture = '/img/people/' + (agent_name || this.account.agent_name).toLowerCase().replace(/\s/, '-') + '.jpg';
return this.account.picture;
};
User.prototype.get = function(p) {
if(!this.loggedIn) throw Error("User#get requires the user to be logged in.");
if(this.account[p]) {
return this.account[p];
}else{
throw Error("User#get cannot return " + p + " - doesn't exist.");
}
};
User.prototype.set = function(p, v) {
EventEmitter.call(this);
if(!this.loggedIn) throw Error("User#set requires the user to be logged in.");
var self = this;
if(typeof p === 'object') {
// Multi item
_.each(_.keys(p), function(item) {
self.account[item] = p[item];
});
}else{
// Single item
if(typeof v === "undefined") throw Error("User#set must pass property and value.");
// Check that we can set this value
if(_.has(this.account, p)) {
this.account[p] = v;
}else{
throw Error("User#set cannot set " + p + " - doesn't exist in _login_. Use User#pref");
}
}
// Unlike Quote#set we can update the _login_ table straight away since these values won't change often
this.save(function(done) {
self.emit('pref saved', done);
});
return this;
};
/*
* Pref is funky. It's a two-pronged function.
* Pass it one value, it'll return it.
* Pass it two values and it sets it.
*/
User.prototype.pref = function(prefName, prefValue, callback) {
EventEmitter.call(this);
if(!this.loggedIn) throw Error("User#pref requires the user to be logged in.");
callback = callback || noop;
// One value passed, return it.
if(typeof prefValue === 'undefined') {
if(this.account.prefs[prefName]) {
return this.account.prefs[prefName];
}else{
return false; // It doesn't exist. No point crying.
}
}
// Set the preference. We have to cheat here since User#set doesn't know about sub-objects.
this.account.prefs[prefName] = prefValue;
this.save(function() {
callback();
});
return this;
};
User.prototype.delPref = function(prefName, callback) {
EventEmitter.call(this);
if(!this.loggedIn) throw Error("User#pref requires the user to be logged in.");
callback = callback || noop;
if(this.account.prefs[prefName]) {
delete this.account.prefs[prefName];
this.save(function() {
callback(true);
});
return true;
}else{
callback(false);
return false; // It doesn't exist. No point crying.
}
return this;
};
User.prototype.save = function(callback) {
EventEmitter.call(this);
if(!this.loggedIn) throw Error("User#save requires the user to be logged in.");
var self = this;
callback = callback || noop;
mysqlPool.getConnection(function(err, db) {
if(err) {
db.end();
throw err;
}
var keyNames = [], keyValues = [];
var tableKeysString = "";
var tmpAccountData = self.account;
_.each(_.keys(tmpAccountData), function(key, index) {
// For preferences, we need to re-encode the object.
if(key !== 'picture') {
if(key == "prefs") tmpAccountData[key] = JSON.stringify(tmpAccountData[key]);
tableKeysString += "`"+key+"` = ?, ";
keyValues.push(tmpAccountData[key]);
}
});
tableKeysString = tableKeysString.substr(0, tableKeysString.length - 2);
db.query("UPDATE _login_ SET " + tableKeysString + " WHERE agent_id = " + self.account.agent_id, keyValues, function(err, rows) {
if(err) {
console.log(this.sql);
self.emit('saved', false);
callback(false);
db.end();
throw err;
}
self.fetch(tmpAccountData.agent_id);
self.once('fetched', function(account) {
if(account) {
self.account = account;
self.emit('saved', true);
callback(true);
}else{
self.emit('saved', false);
callback(false)
}
self.removeAllListeners('fetched');
db.end();
})
});
});
return this;
};
/**
* Removes all EventEmitter listeners.
*/
User.prototype.deafen = function() {
this.removeAllListeners();
};
/**
* Alias for User#find but for active users only.
*/
User.prototype.findActive = function(callback) {
EventEmitter.call(this);
if(typeof callback === "undefined") throw Error("User#findActive does not support EventEmitter yet. A callback is required.");
this.find({
active: 1
}, function(activeUsers) {
callback(activeUsers);
});
return this;
};
/**
* Alias for User#find but by a team.
*/
User.prototype.findInTeam = function(team, callback) {
EventEmitter.call(this);
if(typeof team === "undefined") throw Error("User#findInTeam requires a team number.");
if(typeof callback === "undefined") throw Error("User#findInTeam does not support EventEmitter yet. A callback is required.");
this.find({
team: team
}, function(usersInTeam) {
callback(usersInTeam);
});
return this;
};
/**
* Alias for User#find but for non-users (as in Website).
*/
User.prototype.findNonUsers = function(callback) {
EventEmitter.call(this);
if(typeof callback === "undefined") throw Error("User#findNonUsers does not support EventEmitter yet. A callback is required.");
this.find({
level: 0,
team: 7,
active: 1
}, ['agent_id', 'agent_name'], function(nonUsers) {
callback(nonUsers);
});
return this;
};
/**
* Returns users from _login_, can be filtered by passing an object.
*/
User.prototype.find = function(findBy, selectedFields, callback) {
EventEmitter.call(this);
var self = this, findByValues = [], findByClause = "";
// findBy can only be an object, otherwise it's what selectedFields should be.
if(typeof findBy === 'object') {
findByValues = _.values(findBy);
findByClause = "WHERE ";
_.each(_.keys(findBy), function(value) {
findByClause += "`"+value+"` = ? AND ";
});
findByClause = findByClause.substr(0, findByClause.length - 5);
if(typeof selectedFields === 'function') {
callback = selectedFields;
selectedFields = "*";
}
}else if(typeof findBy === 'array') {
callback = selectedFields;
selectedFields = findBy.join(", ");
}else if(typeof findBy === 'string') {
callback = selectedFields;
selectedFields = findBy;
}else if(typeof findBy === 'function') {
callback = findBy;
selectedFields = "*";
}
mysqlPool.getConnection(function(err, db) {
db.query("SELECT " + selectedFields + " FROM _login_ " + findByClause, findByValues, function(err, results) {
if(err) {
db.end();
callback(false);
}
callback(results);
db.end();
});
});
return this;
};
/**
* Returns sources. Not really users, but it's handy to have them here.
*/
User.prototype.sources = function(callback) {
EventEmitter.call(this);
var sources = [];
if(typeof callback === 'undefined') throw Error("User#sources must be passed a callback.");
mysqlPool.getConnection(function(err, db) {
var lookupSrc = db.query("SELECT * FROM _source_ WHERE src_active = 1");
lookupSrc.on('error', function(err) {
if(err) {
db.end();
throw err;
}
}).on('result', function(row) {
sources[row.src_id] = row;
}).on('end', function() {
callback(sources);
db.end();
});
})
return this;
};
exports = module.exports = User;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment