activator
Version:
simple user activation and password reset for nodejs
110 lines (101 loc) • 3.91 kB
JavaScript
/*jslint node:true, nomen:true, unused:vars */
var fs = require('fs'), _ = require('lodash'), async = require('async'), PATH = __dirname+'/templates', EXPIRY = 60,
// keep a cache of templates
mails = {};
module.exports = function (p) {
var path = p || PATH;
return function (type,lang,callback) {
// build our list from most specific to least-specific
var now = new Date().getTime(), found = false, list = _.reduce(
(lang||"").split('_'),
function(result,item){
result.push( result.length === 0 ? item : [].concat(result[result.length-1],item).join("_") );
return(result);},
[]).reverse();
// default case
list.push("");
// need to get the mail from filesystem if not cached already
// however, we must be careful of the fallback
// e.g. we once sent an email with no matching lang, e.g. he_IL, so the default (item="") was found and cached
// now we look for a different lang, e.g. fr_FR. Since we never looked for it, we will not have it in the cache,
// but we *will* have the default (item=""), which would cause us not to look for fr_FR on the filesystem
// so it is important that we check not only "is it in the cache", but "did we ever look for it"
// our logic is:
// 1- go from most-specific (fr_FR) to less specific (fr) to default ("")
// 2- with each one, if it is cached use it
// 3- if it is not cached but also was not found on the filesystem, go to the next
// essentially, we need a system to track which ones we searched for and when
mails[type] = mails[type] || {};
// look for each type in reverse order
_.each(list,function (item) {
// first, if it is not found at all, then we never searched for it, so go back
if (!mails[type][item]) {
found = false;
return(false);
} else if (!mails[type][item].notfound && mails[type][item].expired > now) {
// so we searched for it. if it was found and is not expired, used it
found = mails[type][item];
return(false);
}
// else either it is expired, or it was notfound, so keep looking for the next less-specific down the line
});
// did we find an answer?
if (found) {
callback(null,found);
} else {
fs.readdir(path,function (err,files) {
var actuals = [];
if (err) {
callback("missingmailfiles");
} else {
_.each(list,function (item) {
var fileName = type+(item?'_'+item:''), txtName = fileName + '.txt', htmlName = fileName + '.html';
if (_.includes(files,fileName)) {
actuals.push({name:item,type:"text",path:path+'/'+fileName});
} else if (_.includes(files,txtName)) {
actuals.push({name:item,type:"text",path:path+'/'+txtName});
}
if (_.includes(files,htmlName)) {
actuals.push({name:item,type:"html",path:path+'/'+htmlName});
}
});
// actuals now contains the actual file names we have
async.each(actuals,
function(item,cb) {
fs.readFile(item.path,'utf8',function (err,data) {
if (data) {
data = data.replace(/\r\n/g,'\n');
data = data.match(/^([^\n]*)\n[^\n]*\n((.|\n)*)/m);
mails[type][item.name] = mails[type][item.name] || {};
mails[type][item.name][item.type] = {
subject: data[1],
content: data[2]
};
mails[type][item.name].expired = now + EXPIRY*60*1000;
cb();
} else {
mails[type][item.name] = {
notfound: true,
expired : now + EXPIRY*60*1000
};
cb();
}
});
},
function (err) {
// there should be no errors, we should just be complete
_.each(list,function (item) {
if (mails[type][item] && mails[type][item].expired > now) {
found = mails[type][item];
return(false);
}
});
// did we find an answer?
callback(null,found);
}
);
}
});
}
};
};