refactor(bs3): sync with develop

This commit is contained in:
Matteo Pagliazzi 2014-02-03 18:45:25 +01:00
commit 56133894d8
18 changed files with 137 additions and 64 deletions

2
.gitignore vendored
View file

@ -12,6 +12,8 @@ newrelic_agent.log
.bower-tmp
.bower-registry
.bower-cache
*.log
src/*/*.map
src/*/*/*.map
test/*.js

View file

@ -6,4 +6,5 @@ Gruntfile.js
CHANGELOG.md
.idea*
.git*
*.log
newrelic_agent.log

View file

@ -78,7 +78,7 @@ module.exports = function(grunt) {
nodemon: {
dev: {
ignoredFiles: ['public/*', 'Gruntfile.js', 'CHANGELOG.md', 'views/*', 'build/*', '.idea*', '.git*']
ignoredFiles: ['public/*', 'Gruntfile.js', 'CHANGELOG.md', 'views/*', 'build/*', '.idea*', '.git*', '*.log']
}
},

View file

@ -11,6 +11,9 @@
"SMTP_USER":"user@domain.com",
"SMTP_PASS":"password",
"SMTP_SERVICE":"Gmail",
"SMTP_HOST":"smtp.gmail.com",
"SMTP_PORT": 587,
"SMTP_TLS": true,
"STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111",
"STRIPE_PUB_KEY":"22223333444455556666777788889999",
"PAYPAL_MERCHANT":"paypal-merchant@gmail.com",

View file

@ -45,7 +45,10 @@
"passport": "~0.1.18",
"passport-facebook": "~1.0.2",
"newrelic": "~1.3.0",
"connect-ratelimit": "0.0.6"
"connect-ratelimit": "0.0.6",
"winston": "~0.7.2",
"winston-mail": "~0.2.7",
"winston-newrelic": "~0.1.4"
},
"private": true,
"subdomain": "habitrpg",

View file

@ -386,7 +386,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
}
$scope.questAbort = function(){
if (!confirm("Are you sure you want to abort this mission? It will abort it for everyone in your party, and you'll lose your quest scroll.")) return;
if (!confirm("Are you sure you want to abort this mission? It will abort it for everyone in your party, all progress will be lost.")) return;
if (!confirm("Are you double sure? Make sure they won't hate you forever!")) return;
$rootScope.party.$questAbort();
}

View file

@ -7,6 +7,7 @@ var shared = require('habitrpg-shared');
var User = require('./../models/user').model;
var Group = require('./../models/group').model;
var Challenge = require('./../models/challenge').model;
var logging = require('./../logging');
var csv = require('express-csv');
var api = module.exports;
@ -214,7 +215,7 @@ api.update = function(req, res){
// Compare whether any changes have been made to tasks. If so, we'll want to sync those changes to subscribers
if (before.isOutdated(req.body)) {
User.find({_id: {$in: saved.members}}, function(err, users){
console.log('Challenge updated, sync to subscribers');
logging.info('Challenge updated, sync to subscribers');
if (err) throw err;
_.each(users, function(user){
saved.syncToUser(user);

View file

@ -12,6 +12,7 @@ var sanitize = validator.sanitize;
var User = require('./../models/user').model;
var Group = require('./../models/group').model;
var Challenge = require('./../models/challenge').model;
var logging = require('./../logging');
var acceptablePUTPaths;
var api = module.exports;
@ -349,7 +350,7 @@ api.buyGemsPaypalIPN = function(req, res, next) {
user.balance += 5;
//user.purchased.ads = true;
user.save();
console.log('PayPal transaction completed and user updated');
logging.info('PayPal transaction completed and user updated');
});
}
});
@ -518,4 +519,4 @@ api.batchUpdate = function(req, res, next) {
res.json(200, {_v: response._v});
}
});
};
};

49
src/logging.js Normal file
View file

@ -0,0 +1,49 @@
var nconf = require('nconf');
var winston = require('winston');
require('winston-mail').Mail;
require('winston-newrelic');
var logger;
if (logger == null) {
logger = new (winston.Logger)({});
if (nconf.get('NODE_ENV') == 'production') {
logger.add(winston.transports.newrelic, {});
logger.add(winston.transports.Mail, {
to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'),
from: "HabitRPG <" + nconf.get('SMTP_USER') + ">",
subject: "HabitRPG Error",
host: nconf.get('SMTP_HOST'),
port: nconf.get('SMTP_PORT'),
tls: nconf.get('SMTP_TLS'),
username: nconf.get('SMTP_USER'),
password: nconf.get('SMTP_PASS'),
level: 'error'
});
} else {
logger.add(winston.transports.Console, {colorize:true});
logger.add(winston.transports.File, {filename: 'habitrpg.log'});
}
}
// A custom log function that wraps Winston. Makes it easy to instrument code
// and still possible to replace Winston in the future.
module.exports.log = function(/* variable args */) {
if (logger)
logger.log.apply(logger, arguments);
};
module.exports.info = function(/* variable args */) {
if (logger)
logger.info.apply(logger, arguments);
};
module.exports.warn = function(/* variable args */) {
if (logger)
logger.warn.apply(logger, arguments);
};
module.exports.error = function(/* variable args */) {
if (logger)
logger.error.apply(logger, arguments);
};

View file

@ -4,6 +4,7 @@ var fs = require('fs');
var path = require('path');
var User = require('./models/user').model
var limiter = require('connect-ratelimit');
var logging = require('./logging');
module.exports.apiThrottle = function(app) {
app.use(limiter({
@ -16,7 +17,7 @@ module.exports.apiThrottle = function(app) {
}
}
})).use(function(req,res,next){
//console.log(res.ratelimit);
//logging.info(res.ratelimit);
if (res.ratelimit.exceeded) return res.json(429,{err:'Rate limit exceeded'});
next();
});
@ -92,15 +93,15 @@ var getManifestFiles = function(page){
var css = '';
_.each(files.css, function(file){
css += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(file) + '">';
css += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(file) + '">';
});
if(nconf.get('NODE_ENV') === 'production'){
return css + '<script type="text/javascript" src="' + getBuildUrl(page + '.js') + '"></script>';
return css + '<script type="text/javascript" src="' + getBuildUrl(page + '.js') + '"></script>';
}else{
var results = css;
_.each(files.js, function(file){
results += '<script type="text/javascript" src="' + getBuildUrl(file) + '"></script>';
results += '<script type="text/javascript" src="' + getBuildUrl(file) + '"></script>';
});
return results;
}
@ -184,16 +185,16 @@ var getUserLanguage = function(req, callback){
}
});
}else{
return callback(null, _.find(avalaibleLanguages, {code: getFromBrowser()}));
return callback(null, _.find(avalaibleLanguages, {code: getFromBrowser()}));
}
}
module.exports.locals = function(req, res, next) {
getUserLanguage(req, function(err, language){
if(err) return res.json(500, {err: err});
if(err) return res.json(500, {err: err});
language.momentLang = (momentLangs[language.code] || undefined);
res.locals.habitrpg = {
NODE_ENV: nconf.get('NODE_ENV'),
BASE_URL: nconf.get('BASE_URL'),
@ -209,11 +210,11 @@ module.exports.locals = function(req, res, next) {
var string = translations[language.code][stringName];
if(!string) return _.template(translations[language.code].stringNotFound, {string: stringName});
return vars === undefined ? string : _.template(string, vars);
return vars === undefined ? string : _.template(string, vars);
},
siteVersion: siteVersion
}
next();
next();
});
}
}

View file

@ -6,6 +6,7 @@ var icalendar = require('icalendar');
var api = require('./../controllers/user');
var auth = require('./../controllers/auth');
var middleware = require('../middleware');
var logging = require('./../logging');
/* ---------- Deprecated API ------------*/
@ -78,7 +79,7 @@ var batchUpdate = function(req, res, next) {
req.body = action.data;
res.send = res.json = function(code, data) {
if (_.isNumber(code) && code >= 400) {
console.error({
logging.error({
code: code,
data: data
});
@ -168,4 +169,4 @@ router.get('*', deprecated);
router.post('*', deprecated);
router.put('*', deprecated);
module.exports = router;
module.exports = router;

View file

@ -1,6 +1,7 @@
require('coffee-script') // for habitrpg-shared
var nconf = require('nconf');
var utils = require('./utils');
var logging = require('./logging');
utils.setupConfig();
var async = require('async');
var mongoose = require('mongoose');
@ -15,7 +16,7 @@ async.waterfall([
Group.findById('habitrpg', cb);
},
function(tavern, cb){
console.log({tavern:tavern,cb:cb});
logging.info({tavern:tavern,cb:cb});
if (!tavern) {
tavern = new Group({
_id: 'habitrpg',
@ -31,6 +32,6 @@ async.waterfall([
}
],function(err){
if (err) throw err;
console.log("Done initializing database");
logging.info("Done initializing database");
mongoose.disconnect();
})

View file

@ -4,7 +4,7 @@ var _ = require('lodash');
var nconf = require('nconf');
var utils = require('./utils');
utils.setupConfig();
var logging = require('./logging');
var isProd = nconf.get('NODE_ENV') === 'production';
var isDev = nconf.get('NODE_ENV') === 'development';
@ -12,11 +12,11 @@ if (cluster.isMaster && (isDev || isProd)) {
// Fork workers.
_.times(require('os').cpus().length, function(){
cluster.fork();
})
});
cluster.on('exit', function(worker, code, signal) {
cluster.on('disconnect', function(worker, code, signal) {
var w = cluster.fork(); // replace the dead worker
console.error('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid);
logging.info('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid);
});
} else {
@ -40,7 +40,7 @@ if (cluster.isMaster && (isDev || isProd)) {
require('./models/challenge');
mongoose.connect(nconf.get('NODE_DB_URI'), {auto_reconnect:true}, function(err) {
if (err) throw err;
console.info('Connected with Mongoose');
logging.info('Connected with Mongoose');
});
@ -135,14 +135,15 @@ if (cluster.isMaster && (isDev || isProd)) {
app.use('/api/v1', require('./routes/apiv1').middleware);
app.use('/export', require('./routes/dataexport').middleware);
app.use(utils.crashWorker(server));
app.use(utils.errorHandler);
require('./routes/apiv2.coffee')(swagger, v2);
server.on('request', app);
server.listen(app.get("port"), function() {
return console.log("Express server listening on port " + app.get("port"));
return logging.info("Express server listening on port " + app.get("port"));
});
module.exports = server;
}
}

View file

@ -2,18 +2,20 @@ var nodemailer = require('nodemailer');
var nconf = require('nconf');
var crypto = require('crypto');
var path = require("path");
var cluster = require("cluster");
module.exports.sendEmail = function(mailData) {
var smtpTransport = nodemailer.createTransport("SMTP",{
service: nconf.get('SMTP_SERVICE'),
service: nconf.get('SMTP_SERVICE'),
auth: {
user: nconf.get('SMTP_USER'),
pass: nconf.get('SMTP_PASS')
user: nconf.get('SMTP_USER'),
pass: nconf.get('SMTP_PASS')
}
});
smtpTransport.sendMail(mailData, function(error, response){
if(error) console.log(error);
else console.log("Message sent: " + response.message);
var logging = require('./logging');
if(error) logging.error(error);
else logging.info("Message sent: " + response.message);
smtpTransport.close(); // shut down the connection pool, no more messages
});
}
@ -48,7 +50,8 @@ module.exports.setupConfig = function(){
// // * https://developers.google.com/chrome-developer-tools/docs/heap-profiling
// // * https://developers.google.com/chrome-developer-tools/docs/memory-analysis-101
// agent = require('webkit-devtools-agent');
// console.log("To debug memory leaks:" +
// var logging = require('./logging');
// logging.info("To debug memory leaks:" +
// "\n\t(1) Run `kill -SIGUSR2 " + process.pid + "`" +
// "\n\t(2) open http://c4milo.github.com/node-webkit-agent/21.0.1180.57/inspector.html?host=localhost:1337&page=0");
// }
@ -59,6 +62,23 @@ module.exports.setupConfig = function(){
if (nconf.get('NODE_ENV') === 'production') require('newrelic');
};
module.exports.crashWorker = function(server) {
return function(err, req, res, next) {
if (!cluster.isMaster) {
// make sure we close down within 30 seconds
var killtimer = setTimeout(function() {
process.exit(1);
}, 30000);
// But don't keep the process open just for that!
killtimer.unref();
// stop taking new requests.
server.close();
cluster.worker.disconnect();
}
next(err);
};
}
module.exports.errorHandler = function(err, req, res, next) {
// 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)
@ -69,15 +89,9 @@ module.exports.errorHandler = function(err, req, res, next) {
"\n\nheaders: " + JSON.stringify(req.headers) +
"\n\nbody: " + JSON.stringify(req.body) +
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
module.exports.sendEmail({
from: "HabitRPG <" + nconf.get('SMTP_USER') + ">",
to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'),
subject: "HabitRPG Error",
text: stack
});
console.error(stack);
var logging = require('./logging');
logging.error(stack);
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);
process.exit(0);
}
}

View file

@ -54,7 +54,7 @@ script(type='text/ng-template', id='partials/options.inventory.pets.html')
menu
div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.text}}', popover-placement='bottom', ng-init='pet = egg.key+"-"+potion.key')
button(class="pet-button Pet-{{pet}}", ng-if='user.items.pets[pet]>0', ng-class='{active: user.items.currentPet == pet, selectableInventory: selectedFood}', ng-click='choosePet(egg.key, potion.key)')
.progress(ng-class='{"progress-success": user.items.pets[pet]<50}')
.progress(ng-show='!user.items.mounts[pet]', ng-class='{"progress-success": user.items.pets[pet]<50}')
.bar(style="width: {{user.items.pets[pet]/.5}}%;")
button(class="pet-button pet-not-owned", ng-if='!user.items.pets[pet]')
.PixelPaw

View file

@ -30,22 +30,15 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
| 2 <span class="Pet_Currency_Gem1x inline-gems"/> /
= ' ' + env.t('locked')
menu(type='list')
// For special events code, see commit dfa27b3
// Color
li.customize-menu
menu(label=env.t('color'))
each v,k in {'c8c8c8':'white','903a00':'brown','cfb853':'blond','ec720f':'red','2e2e2e':'black'}
button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:##{k};', ng-click='set({"preferences.hair.color": "#{v}"})')
// Special Events
li.customize-menu.well.limited-edition
.label.label-info.pull-right(popover=env.t('limited31Jan'), popover-title=env.t('limitedEdition'), popover-placement='right', popover-trigger='mouseenter')
=env.t('limitedEdition')
|
span.glyphicon.glyphicon-question-sign
menu(label=env.t('winterColors'))
each color in ['candycane','frost','winternight','holly']
button(type='button', ng-class='{locked: !user.purchased.hair.color.#{color}}', class='customize-option hair_bangs_1_#{color}', style='width: 40px; height: 40px;', ng-click='unlock("hair.color.#{color}")')
button.btn.btn-small.btn-primary(ng-hide='user.purchased.hair.color.candycane && user.purchased.hair.color.frost && user.purchased.hair.color.winternight && user.purchased.hair.color.holly', ng-click='unlock("hair.color.candycane,hair.color.frost,hair.color.winternight,hair.color.holly")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
button(type='button', ng-if='user.purchased.hair.color.#{color}', class='customize-option hair_bangs_1_#{color}', style='width: 40px; height: 40px;', ng-click='unlock("hair.color.#{color}")')
h5=env.t('bodyHair')
// Bangs
@ -160,22 +153,23 @@ script(id='partials/options.profile.stats.html', type='text/ng-template')
span.glyphicon.glyphicon-download
&nbsp;
=env.t('distributePoints')
tr
td= env.t('allocateStr') + ' {{user.stats.str}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("str")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateStrPop')) +
tr
td= env.t('allocateCon') + ' {{user.stats.con}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("con")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateConPop')) +
tr
td= env.t('allocateInt') + ' {{user.stats.int}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("int")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateIntPop')) +
tr
td= env.t('allocatePer') + ' {{user.stats.per}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("per")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocatePerPop')) +
tr
td= env.t('allocateInt') + ' {{user.stats.int}}'
td= env.t('allocateStr') + ' {{user.stats.str}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("int")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateIntPop')) +
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("str")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateStrPop')) +
div(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "span4" : "span6"')
include ../shared/profiles/achievements

View file

@ -63,15 +63,16 @@ table.table.table-striped
|: {{profile.stats.buffs[k] || 0}}&nbsp;
tr(ng-if='profile.stats.buffs.stealth')
td
i.icon-question-sign(popover-title=env.t('stealth'), popover-trigger='mouseenter', popover-placement='right', popover=env.t('stealthNewDay'))
&nbsp;
strong=env.t('stealth')
|: {{profile.stats.buffs.stealth}}&nbsp;
span.glyphicon.glyphicon-question-sign(popover-title=env.t('stealth'), popover-trigger='mouseenter', popover-placement='right', popover=env.t('stealthNewDay'))
|: {{profile.stats.buffs.stealth}}
td
tr(ng-if='profile.stats.buffs.streaks')
td
i.icon-question-sign(popover-title=env.t('streaksFrozen'), popover-trigger='mouseenter', popover-placement='right', popover=env.t('streaksFrozenText'))
&nbsp;
strong=env.t('streaksFrozen')
|
span.glyphicon.glyphicon-question-sign(popover-title=env.t('streaksFrozen'), popover-trigger='mouseenter', popover-placement='right', popover=env.t('streaksFrozenText'))
td
h4(ng-show='user.flags.dropsEnabled')=env.t('pets')

View file

@ -17,7 +17,7 @@ li(bindonce='list', bo-id='"task-"+task.id', ng-repeat='task in obj[list.type+"s
a.badge(ng-if='task.checklist[0]', ng-class='{"badge-success":checklistCompletion(task.checklist) == task.checklist.length}', ng-click='collapseChecklist(task)', style='margin:1px')
{{checklistCompletion(task.checklist)}}/{{task.checklist.length}}
| &nbsp;
span.glyphicon.glyphicon-tags(tooltip='{{appliedTags(user.tags, task.tags)}}', ng-hide='Shared.noTags(task.tags)')
span.glyphicon.glyphicon-tags(tooltip='{{Shared.appliedTags(user.tags, task.tags)}}', ng-hide='Shared.noTags(task.tags)')
// edit
a(ng-hide='task._editing', ng-click='editTask(task)', tooltip=env.t('edit'))
| &nbsp;