mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
Implements a base-pet-themed set of avatar skins and head accessories, the latter of which has additional handling for the user to purchase them from the Avatar Customization page.
213 lines
7.5 KiB
JavaScript
213 lines
7.5 KiB
JavaScript
var nconf = require('nconf');
|
|
var _ = require('lodash');
|
|
var fs = require('fs');
|
|
var path = require('path');
|
|
var User = require('./models/user').model
|
|
var limiter = require('connect-ratelimit');
|
|
var logging = require('./logging');
|
|
var domainMiddleware = require('domain-middleware');
|
|
var cluster = require('cluster');
|
|
var i18n = require('./i18n.js');
|
|
var shared = require('../../common');
|
|
var request = require('request');
|
|
var os = require('os');
|
|
var moment = require('moment');
|
|
var utils = require('./utils');
|
|
|
|
module.exports.apiThrottle = function(app) {
|
|
if (nconf.get('NODE_ENV') !== 'production') return;
|
|
app.use(limiter({
|
|
end:false,
|
|
catagories:{
|
|
normal: {
|
|
// 2 req/s, but split as minutes
|
|
totalRequests: 80,
|
|
every: 60000
|
|
}
|
|
}
|
|
})).use(function(req,res,next){
|
|
//logging.info(res.ratelimit);
|
|
if (res.ratelimit.exceeded) return res.json(429,{err:'Rate limit exceeded'});
|
|
next();
|
|
});
|
|
}
|
|
|
|
module.exports.domainMiddleware = function(server,mongoose) {
|
|
if (nconf.get('NODE_ENV')=='production') {
|
|
var mins = 3, // how often to run this check
|
|
useAvg = false, // use average over 3 minutes, or simply the last minute's report
|
|
url = 'https://api.newrelic.com/v2/applications/'+nconf.get('NEW_RELIC_APPLICATION_ID')+'/metrics/data.json?names[]=Apdex&values[]=score';
|
|
setInterval(function(){
|
|
// see https://docs.newrelic.com/docs/apm/apis/api-v2-examples/average-response-time-examples-api-v2, https://rpm.newrelic.com/api/explore/applications/data
|
|
request({
|
|
url: useAvg ? url+'&from='+moment().subtract({minutes:mins}).utc().format()+'&to='+moment().utc().format()+'&summarize=true' : url,
|
|
headers: {'X-Api-Key': nconf.get('NEW_RELIC_API_KEY')}
|
|
}, function(err, response, body){
|
|
var ts = JSON.parse(body).metric_data.metrics[0].timeslices,
|
|
score = ts[ts.length-1].values.score,
|
|
apdexBad = score < .75 || score == 1,
|
|
memory = os.freemem() / os.totalmem(),
|
|
memoryHigh = false; //memory < 0.1;
|
|
if (apdexBad || memoryHigh) throw "[Memory Leak] Apdex="+score+" Memory="+parseFloat(memory).toFixed(3)+" Time="+moment().format();
|
|
})
|
|
}, mins*60*1000);
|
|
}
|
|
|
|
return domainMiddleware({
|
|
server: {
|
|
close:function(){
|
|
server.close();
|
|
mongoose.connection.close();
|
|
}
|
|
},
|
|
killTimeout: 10000
|
|
});
|
|
}
|
|
|
|
module.exports.errorHandler = function(err, req, res, next) {
|
|
//res.locals.domain.emit('error', err);
|
|
// when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER)
|
|
var stack = (err.stack ? err.stack : err.message ? err.message : err) +
|
|
"\n ----------------------------\n" +
|
|
"\n\noriginalUrl: " + req.originalUrl +
|
|
"\n\nauth: " + req.headers['x-api-user'] + ' | ' + req.headers['x-api-key'] +
|
|
"\n\nheaders: " + JSON.stringify(req.headers) +
|
|
"\n\nbody: " + JSON.stringify(req.body) +
|
|
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
|
|
logging.error(stack);
|
|
/*logging.loggly({
|
|
error: "Uncaught error",
|
|
stack: (err.stack || err.message || err),
|
|
body: req.body, headers: req.header,
|
|
auth: req.headers['x-api-user'],
|
|
originalUrl: req.originalUrl
|
|
});*/
|
|
var message = err.message ? err.message : err;
|
|
message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length);
|
|
res.json(500,{err:message}); //res.end(err.message);
|
|
}
|
|
|
|
|
|
module.exports.forceSSL = function(req, res, next){
|
|
var baseUrl = nconf.get("BASE_URL");
|
|
// Note x-forwarded-proto is used by Heroku & nginx, you'll have to do something different if you're not using those
|
|
if (req.headers['x-forwarded-proto'] && req.headers['x-forwarded-proto'] !== 'https'
|
|
&& nconf.get('NODE_ENV') === 'production'
|
|
&& baseUrl.indexOf('https') === 0) {
|
|
return res.redirect(baseUrl + req.url);
|
|
}
|
|
next()
|
|
}
|
|
|
|
module.exports.cors = function(req, res, next) {
|
|
res.header("Access-Control-Allow-Origin", req.headers.origin || "*");
|
|
res.header("Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,HEAD,DELETE");
|
|
res.header("Access-Control-Allow-Headers", "Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key");
|
|
if (req.method === 'OPTIONS') return res.send(200);
|
|
return next();
|
|
};
|
|
|
|
var siteVersion = 1;
|
|
|
|
module.exports.forceRefresh = function(req, res, next){
|
|
if(req.query.siteVersion && req.query.siteVersion != siteVersion){
|
|
return res.json(400, {needRefresh: true});
|
|
}
|
|
|
|
return next();
|
|
};
|
|
|
|
var buildFiles = [];
|
|
|
|
var walk = function(folder){
|
|
var res = fs.readdirSync(folder);
|
|
|
|
res.forEach(function(fileName){
|
|
file = folder + '/' + fileName;
|
|
if(fs.statSync(file).isDirectory()){
|
|
walk(file);
|
|
}else{
|
|
var relFolder = path.relative(path.join(__dirname, "/../build"), folder);
|
|
var old = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1');
|
|
|
|
if(relFolder){
|
|
old = relFolder + '/' + old;
|
|
fileName = relFolder + '/' + fileName;
|
|
}
|
|
|
|
buildFiles[old] = fileName
|
|
}
|
|
});
|
|
}
|
|
|
|
walk(path.join(__dirname, "/../build"));
|
|
|
|
var getBuildUrl = function(url){
|
|
if(buildFiles[url]) return '/' + buildFiles[url];
|
|
|
|
return '/' + url;
|
|
}
|
|
|
|
var manifestFiles = require("../public/manifest.json");
|
|
|
|
var getManifestFiles = function(page){
|
|
var files = manifestFiles[page];
|
|
|
|
if(!files) throw new Error("Page not found!");
|
|
|
|
var code = '';
|
|
|
|
if(nconf.get('NODE_ENV') === 'production'){
|
|
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(page + '.css') + '">';
|
|
code += '<script type="text/javascript" src="' + getBuildUrl(page + '.js') + '"></script>';
|
|
}else{
|
|
_.each(files.css, function(file){
|
|
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(file) + '">';
|
|
});
|
|
_.each(files.js, function(file){
|
|
code += '<script type="text/javascript" src="' + getBuildUrl(file) + '"></script>';
|
|
});
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
module.exports.locals = function(req, res, next) {
|
|
var language = _.find(i18n.avalaibleLanguages, {code: req.language});
|
|
var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/'
|
|
|
|
// Load moment.js language file only when not on static pages
|
|
language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
|
|
|
|
var tavern = require('./models/group').tavern;
|
|
var envVars = _.pick(nconf.get(), 'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY'.split(' '));
|
|
res.locals.habitrpg = _.merge(envVars, {
|
|
IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
|
|
getManifestFiles: getManifestFiles,
|
|
getBuildUrl: getBuildUrl,
|
|
avalaibleLanguages: i18n.avalaibleLanguages,
|
|
language: language,
|
|
isStaticPage: isStaticPage,
|
|
translations: i18n.translations[language.code],
|
|
t: function(){ // stringName and vars are the allowed parameters
|
|
var args = Array.prototype.slice.call(arguments, 0);
|
|
args.push(language.code);
|
|
return shared.i18n.t.apply(null, args);
|
|
},
|
|
siteVersion: siteVersion,
|
|
Content: shared.content,
|
|
mods: require('./models/user').mods,
|
|
tavern: tavern, // for world boss
|
|
worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {},
|
|
_: _
|
|
});
|
|
|
|
// Put query-string party (& guild but use partyInvite for backward compatibility)
|
|
// invitations into session to be handled later
|
|
try{
|
|
req.session.partyInvite = JSON.parse(utils.decrypt(req.query.partyInvite))
|
|
} catch(e){}
|
|
|
|
next();
|
|
}
|