From fe04f371189776b0d28a3ecc7ef06594a3295aef Mon Sep 17 00:00:00 2001 From: Ben Lind Date: Tue, 22 Jul 2014 16:55:50 -0400 Subject: [PATCH 01/56] Fixes #3260: Buttonless habits full width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the user has neither a plus nor a minus button on one of their habits, the habit’s title will now span the entire width of the task div instead of having a gap to the left. --- public/css/tasks.styl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/css/tasks.styl b/public/css/tasks.styl index 5da7b0443a..d201c3deb6 100644 --- a/public/css/tasks.styl +++ b/public/css/tasks.styl @@ -147,6 +147,8 @@ for $stage in $stages .habit-wide .task-text padding-left: 7em +.habit-narrow .task-text + padding-left: 0.75em // when a task is being dragged .task.ui-sortable-helper From 2ea12d5c713538cf20d60516cf0c9f97668f4386 Mon Sep 17 00:00:00 2001 From: Negue Date: Sat, 22 Nov 2014 20:40:18 +0100 Subject: [PATCH 02/56] Add in-app-purchase Package --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index fc3edb043b..061d4a227e 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "grunt-nodemon": "~0.3.0", "habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared#develop", "icalendar": "git://github.com/lefnire/node-icalendar#master", + "in-app-purchase": "^0.2.0", "jade": "~1.7.0", "js2xmlparser": "~0.1.2", "lodash": "~2.4.1", From cc4812712a2a29e742db4ea5301bd7ce4faa0c84 Mon Sep 17 00:00:00 2001 From: Negue Date: Sun, 23 Nov 2014 01:50:03 +0100 Subject: [PATCH 03/56] dummy iap verify routes --- config.json.example | 3 +- src/controllers/payments/iap.js | 63 +++++++++++++++++++++++++++++++ src/controllers/payments/index.js | 7 +++- src/routes/payments.js | 3 ++ 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 src/controllers/payments/iap.js diff --git a/config.json.example b/config.json.example index d8bb67b62e..caedcf3ae9 100644 --- a/config.json.example +++ b/config.json.example @@ -41,5 +41,6 @@ "mode":"sandbox", "client_id":"client_id", "client_secret":"client_secret" - } + }, + "IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/" } diff --git a/src/controllers/payments/iap.js b/src/controllers/payments/iap.js new file mode 100644 index 0000000000..f4c91b663f --- /dev/null +++ b/src/controllers/payments/iap.js @@ -0,0 +1,63 @@ +var iap = require('in-app-purchase'); +var async = require('async'); +var payments = require('./index'); + +/* +For google iap, you need to name your public key file as: +iap-sanbox or iap-live +*/ +iap.config({ + googlePublicKeyPath: "/path/to/google/public/key/dir/" +}); + +exports.androidVerify = function(req, res, next) { + console.info(req.body); + + var token = req.body.id; + var user = res.locals.user; + +iap.setup(function (error) { + if (error) { + console.error('something went wrong...'); + console.error(error); + + return; + } + /* + google receipt must be provided as an object + { + "data": "{stringified data object}", + "signature": "signature from google" + } + */ + // iap is ready + /*iap.validate(iap.GOOGLE, googleReceipt, function (err, googleRes) { + if (err) { + return console.error(err); + } + if (iap.isValidated(googleRes)) { + // yay good! + } + });*/ +}); + +/* + async.waterfall([ + function(response, cb) { + payments.buyGems(user, {customerId: response.id, paymentMethod: 'IAP'}); + + user.save(cb); + } + ], function(err, saved){ + if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors + res.send(200); + user = token = null; + });*/ +}; + +exports.iosVerify = function(req, res, next) { + console.info(req.body); + + var token = req.body.id; + var user = res.locals.user; +}; \ No newline at end of file diff --git a/src/controllers/payments/index.js b/src/controllers/payments/index.js index 176098b4c5..b0c59f643c 100644 --- a/src/controllers/payments/index.js +++ b/src/controllers/payments/index.js @@ -7,9 +7,9 @@ var moment = require('moment'); var isProduction = nconf.get("NODE_ENV") === "production"; var stripe = require('./stripe'); var paypal = require('./paypal'); -var User = require('mongoose').model('User'); var members = require('../members') var async = require('async'); +var iap = require('./iap'); function revealMysteryItems(user) { _.each(shared.content.gear.flat, function(item) { @@ -123,4 +123,7 @@ exports.paypalSubscribeSuccess = paypal.executeBillingAgreement; exports.paypalSubscribeCancel = paypal.cancelSubscription; exports.paypalCheckout = paypal.createPayment; exports.paypalCheckoutSuccess = paypal.executePayment; -exports.paypalIPN = paypal.ipn; \ No newline at end of file +exports.paypalIPN = paypal.ipn; + +exports.iapAndroidVerify = iap.androidVerify; +exports.iapIosVerify = iap.iosVerify; diff --git a/src/routes/payments.js b/src/routes/payments.js index c34d77b24b..8cd6b37ef7 100644 --- a/src/routes/payments.js +++ b/src/routes/payments.js @@ -17,4 +17,7 @@ router.post("/stripe/subscribe/edit", auth.auth, i18n.getUserLanguage, payments. //router.get("/stripe/subscribe", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead router.get("/stripe/subscribe/cancel", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribeCancel); +router.post("/iap/android/verify", auth.authWithUrl, i18n.getUserLanguage, payments.iapAndroidVerify); +router.post("/iap/ios/verify", auth.authWithUrl, i18n.getUserLanguage, payments.iapIosVerify); + module.exports = router; \ No newline at end of file From b59a70f1285a8d8ac10cc3229fcd7852c2f01cf9 Mon Sep 17 00:00:00 2001 From: Negue Date: Mon, 24 Nov 2014 20:20:10 +0100 Subject: [PATCH 04/56] more dummy controller, just for iap data --- src/controllers/payments/iap.js | 8 -------- src/routes/payments.js | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/controllers/payments/iap.js b/src/controllers/payments/iap.js index f4c91b663f..2947d44505 100644 --- a/src/controllers/payments/iap.js +++ b/src/controllers/payments/iap.js @@ -2,14 +2,6 @@ var iap = require('in-app-purchase'); var async = require('async'); var payments = require('./index'); -/* -For google iap, you need to name your public key file as: -iap-sanbox or iap-live -*/ -iap.config({ - googlePublicKeyPath: "/path/to/google/public/key/dir/" -}); - exports.androidVerify = function(req, res, next) { console.info(req.body); diff --git a/src/routes/payments.js b/src/routes/payments.js index 8cd6b37ef7..5e77417123 100644 --- a/src/routes/payments.js +++ b/src/routes/payments.js @@ -17,7 +17,7 @@ router.post("/stripe/subscribe/edit", auth.auth, i18n.getUserLanguage, payments. //router.get("/stripe/subscribe", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead router.get("/stripe/subscribe/cancel", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribeCancel); -router.post("/iap/android/verify", auth.authWithUrl, i18n.getUserLanguage, payments.iapAndroidVerify); -router.post("/iap/ios/verify", auth.authWithUrl, i18n.getUserLanguage, payments.iapIosVerify); +router.post("/iap/android/verify", /*auth.authWithUrl, i18n.getUserLanguage, */payments.iapAndroidVerify); +router.post("/iap/ios/verify", /*auth.authWithUrl, i18n.getUserLanguage, */ payments.iapIosVerify); module.exports = router; \ No newline at end of file From ecf20f423cf31d5a8f79fe5c7db5b627ba6044f4 Mon Sep 17 00:00:00 2001 From: Negue Date: Thu, 27 Nov 2014 21:25:15 +0100 Subject: [PATCH 05/56] IAP Verification for Android --- src/controllers/payments/iap.js | 70 +++++++++++++++++++++++---------- src/routes/payments.js | 2 +- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/controllers/payments/iap.js b/src/controllers/payments/iap.js index 2947d44505..daf1627212 100644 --- a/src/controllers/payments/iap.js +++ b/src/controllers/payments/iap.js @@ -1,20 +1,37 @@ var iap = require('in-app-purchase'); var async = require('async'); var payments = require('./index'); +var nconf = require('nconf'); + +var inAppPurchase = require('in-app-purchase'); +inAppPurchase.config({ + googlePublicKeyPath: nconf.get("IAP_GOOGLE_KEYDIR") // this is the path to the directory containing iap-sanbox/iap-live files +}); + +// Validation ERROR Codes +var INVALID_PAYLOAD = 6778001; +var CONNECTION_FAILED = 6778002; +var PURCHASE_EXPIRED = 6778003; exports.androidVerify = function(req, res, next) { - console.info(req.body); - - var token = req.body.id; + var iapBody = req.body; var user = res.locals.user; -iap.setup(function (error) { + iap.setup(function (error) { if (error) { - console.error('something went wrong...'); + var resObj = { + ok: false, + data: 'IAP Error' + }; + + console.error('IAP Setup ERROR'); console.error(error); + res.json(resObj); + return; } + /* google receipt must be provided as an object { @@ -22,29 +39,40 @@ iap.setup(function (error) { "signature": "signature from google" } */ + var testObj = { + data: iapBody.transaction.receipt, + signature: iapBody.transaction.signature + }; + // iap is ready - /*iap.validate(iap.GOOGLE, googleReceipt, function (err, googleRes) { + iap.validate(iap.GOOGLE, testObj, function (err, googleRes) { if (err) { - return console.error(err); + var resObj = { + ok: false, + data: { + code: INVALID_PAYLOAD, + message: err.toString() + } + }; + + res.json(resObj); + console.error(err); + return; } if (iap.isValidated(googleRes)) { + var resObj = { + ok: true, + data: googleRes + }; + + payments.buyGems(user, {customerId:user.id, paymentMethod:'IAP Android'}); + user.save(); + // yay good! + res.json(resObj); } - });*/ + }); }); - -/* - async.waterfall([ - function(response, cb) { - payments.buyGems(user, {customerId: response.id, paymentMethod: 'IAP'}); - - user.save(cb); - } - ], function(err, saved){ - if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors - res.send(200); - user = token = null; - });*/ }; exports.iosVerify = function(req, res, next) { diff --git a/src/routes/payments.js b/src/routes/payments.js index 5e77417123..adc9a65629 100644 --- a/src/routes/payments.js +++ b/src/routes/payments.js @@ -17,7 +17,7 @@ router.post("/stripe/subscribe/edit", auth.auth, i18n.getUserLanguage, payments. //router.get("/stripe/subscribe", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead router.get("/stripe/subscribe/cancel", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribeCancel); -router.post("/iap/android/verify", /*auth.authWithUrl, i18n.getUserLanguage, */payments.iapAndroidVerify); +router.post("/iap/android/verify", auth.authWithUrl, /*i18n.getUserLanguage, */payments.iapAndroidVerify); router.post("/iap/ios/verify", /*auth.authWithUrl, i18n.getUserLanguage, */ payments.iapIosVerify); module.exports = router; \ No newline at end of file From d92832bfb2873b7a768272d459dc0d905929e97f Mon Sep 17 00:00:00 2001 From: Negue Date: Fri, 28 Nov 2014 23:44:42 +0100 Subject: [PATCH 06/56] Hopefully a working IOS Verification --- src/controllers/payments/iap.js | 49 +++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/controllers/payments/iap.js b/src/controllers/payments/iap.js index daf1627212..05eb3ff8f7 100644 --- a/src/controllers/payments/iap.js +++ b/src/controllers/payments/iap.js @@ -65,7 +65,7 @@ exports.androidVerify = function(req, res, next) { data: googleRes }; - payments.buyGems(user, {customerId:user.id, paymentMethod:'IAP Android'}); + payments.buyGems(user, {customerId:user.id, paymentMethod:'IAP GooglePlay'}); user.save(); // yay good! @@ -78,6 +78,51 @@ exports.androidVerify = function(req, res, next) { exports.iosVerify = function(req, res, next) { console.info(req.body); - var token = req.body.id; + var iapBody = req.body; var user = res.locals.user; + + iap.setup(function (error) { + if (error) { + var resObj = { + ok: false, + data: 'IAP Error' + }; + + console.error('IAP Setup ERROR'); + console.error(error); + + res.json(resObj); + + return; + } + + // iap is ready + iap.validate(iap.APPLE, iapBody.transaction.receipt, function (err, appleRes) { + if (err) { + var resObj = { + ok: false, + data: { + code: INVALID_PAYLOAD, + message: err.toString() + } + }; + + res.json(resObj); + console.error(err); + return; + } + if (iap.isValidated(appleRes)) { + var resObj = { + ok: true, + data: appleRes + }; + + payments.buyGems(user, {customerId:user.id, paymentMethod:'IAP AppleStore'}); + user.save(); + + // yay good! + res.json(resObj); + } + }); +}); }; \ No newline at end of file From bb37c163cf1bca94656d5a962dcc9344f1843cbc Mon Sep 17 00:00:00 2001 From: Negue Date: Mon, 8 Dec 2014 22:21:32 +0100 Subject: [PATCH 07/56] fix spacing --- src/controllers/payments/iap.js | 151 ++++++++++++++++---------------- 1 file changed, 77 insertions(+), 74 deletions(-) diff --git a/src/controllers/payments/iap.js b/src/controllers/payments/iap.js index 05eb3ff8f7..f7d44fcc7b 100644 --- a/src/controllers/payments/iap.js +++ b/src/controllers/payments/iap.js @@ -5,7 +5,8 @@ var nconf = require('nconf'); var inAppPurchase = require('in-app-purchase'); inAppPurchase.config({ - googlePublicKeyPath: nconf.get("IAP_GOOGLE_KEYDIR") // this is the path to the directory containing iap-sanbox/iap-live files + // this is the path to the directory containing iap-sanbox/iap-live files + googlePublicKeyPath: nconf.get("IAP_GOOGLE_KEYDIR") }); // Validation ERROR Codes @@ -19,24 +20,24 @@ exports.androidVerify = function(req, res, next) { iap.setup(function (error) { if (error) { - var resObj = { - ok: false, - data: 'IAP Error' - }; + var resObj = { + ok: false, + data: 'IAP Error' + }; - console.error('IAP Setup ERROR'); - console.error(error); + console.error('IAP Setup ERROR'); + console.error(error); - res.json(resObj); + res.json(resObj); - return; + return; } /* - google receipt must be provided as an object - { - "data": "{stringified data object}", - "signature": "signature from google" + google receipt must be provided as an object + { + "data": "{stringified data object}", + "signature": "signature from google" } */ var testObj = { @@ -46,33 +47,34 @@ exports.androidVerify = function(req, res, next) { // iap is ready iap.validate(iap.GOOGLE, testObj, function (err, googleRes) { - if (err) { - var resObj = { - ok: false, - data: { - code: INVALID_PAYLOAD, - message: err.toString() - } - }; - - res.json(resObj); - console.error(err); - return; - } - if (iap.isValidated(googleRes)) { - var resObj = { - ok: true, - data: googleRes - }; + if (err) { + var resObj = { + ok: false, + data: { + code: INVALID_PAYLOAD, + message: err.toString() + } + }; + + res.json(resObj); + console.error(err); + return; + } + + if (iap.isValidated(googleRes)) { + var resObj = { + ok: true, + data: googleRes + }; + + payments.buyGems(user, {customerId:user.id, paymentMethod:'IAP GooglePlay'}); + user.save(); - payments.buyGems(user, {customerId:user.id, paymentMethod:'IAP GooglePlay'}); - user.save(); - - // yay good! - res.json(resObj); - } + // yay good! + res.json(resObj); + } }); -}); + }); }; exports.iosVerify = function(req, res, next) { @@ -83,46 +85,47 @@ exports.iosVerify = function(req, res, next) { iap.setup(function (error) { if (error) { - var resObj = { - ok: false, - data: 'IAP Error' - }; - - console.error('IAP Setup ERROR'); - console.error(error); - - res.json(resObj); - - return; + var resObj = { + ok: false, + data: 'IAP Error' + }; + + console.error('IAP Setup ERROR'); + console.error(error); + + res.json(resObj); + + return; } // iap is ready iap.validate(iap.APPLE, iapBody.transaction.receipt, function (err, appleRes) { - if (err) { - var resObj = { - ok: false, - data: { - code: INVALID_PAYLOAD, - message: err.toString() - } - }; - - res.json(resObj); - console.error(err); - return; - } - if (iap.isValidated(appleRes)) { - var resObj = { - ok: true, - data: appleRes - }; + if (err) { + var resObj = { + ok: false, + data: { + code: INVALID_PAYLOAD, + message: err.toString() + } + }; + + res.json(resObj); + console.error(err); + return; + } + + if (iap.isValidated(appleRes)) { + var resObj = { + ok: true, + data: appleRes + }; + + payments.buyGems(user, {customerId:user.id, paymentMethod:'IAP AppleStore'}); + user.save(); - payments.buyGems(user, {customerId:user.id, paymentMethod:'IAP AppleStore'}); - user.save(); - - // yay good! - res.json(resObj); - } + // yay good! + res.json(resObj); + } }); -}); + }); }; \ No newline at end of file From f9cc2adda548a15b296cb9de2d8ced137752e4be Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 10 Dec 2014 14:25:00 -0700 Subject: [PATCH 08/56] fix(iap): iap verification fixes --- src/controllers/payments/iap.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/controllers/payments/iap.js b/src/controllers/payments/iap.js index f7d44fcc7b..29b03fedfb 100644 --- a/src/controllers/payments/iap.js +++ b/src/controllers/payments/iap.js @@ -67,9 +67,8 @@ exports.androidVerify = function(req, res, next) { data: googleRes }; - payments.buyGems(user, {customerId:user.id, paymentMethod:'IAP GooglePlay'}); - user.save(); - + payments.buyGems({user:user, paymentMethod:'IAP GooglePlay'}); + // yay good! res.json(resObj); } @@ -120,8 +119,7 @@ exports.iosVerify = function(req, res, next) { data: appleRes }; - payments.buyGems(user, {customerId:user.id, paymentMethod:'IAP AppleStore'}); - user.save(); + payments.buyGems({user:user, paymentMethod:'IAP AppleStore'}); // yay good! res.json(resObj); From 3027ccff83c13400a18184361be3a857f910d975 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 11 Dec 2014 21:06:38 -0700 Subject: [PATCH 09/56] fix(plans): fix fields which are NaN --- migrations/20141211_NaN_consecutives.js | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 migrations/20141211_NaN_consecutives.js diff --git a/migrations/20141211_NaN_consecutives.js b/migrations/20141211_NaN_consecutives.js new file mode 100644 index 0000000000..2822df0267 --- /dev/null +++ b/migrations/20141211_NaN_consecutives.js @@ -0,0 +1,4 @@ +db.users.update({'purchased.plan.consecutive.count':NaN}, {$set:{'purchased.plan.consecutive.count':0}}, {multi:1}); +db.users.update({'purchased.plan.consecutive.offset':NaN}, {$set:{'purchased.plan.consecutive.offset':0}}, {multi:1}); +db.users.update({'purchased.plan.consecutive.gemCapExtra':NaN}, {$set:{'purchased.plan.consecutive.gemCapExtra':0}}, {multi:1}); +db.users.update({'purchased.plan.consecutive.trinkets':NaN}, {$set:{'purchased.plan.consecutive.trinkets':0}}, {multi:1}); \ No newline at end of file From ae762837e2846ddc6deda0f52c14c8a3b1f9c888 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 11 Dec 2014 21:20:22 -0700 Subject: [PATCH 10/56] refactor(challenges): cleanup challenge-creation. can use async.auto to keep variables around, no need to set parent closure vars --- src/controllers/challenges.js | 104 ++++++++++++++++------------------ 1 file changed, 49 insertions(+), 55 deletions(-) diff --git a/src/controllers/challenges.js b/src/controllers/challenges.js index b5fe4d6982..d09decf572 100644 --- a/src/controllers/challenges.js +++ b/src/controllers/challenges.js @@ -147,69 +147,63 @@ api.getMember = function(req, res, next) { // CREATE api.create = function(req, res, next){ var user = res.locals.user; - var group, chal; - // First, make sure they've selected a legit group, and store it for later - var waterfall = [ - function(cb){ - Group.findById(req.body.group).exec(cb); + async.auto({ + get_group: function(cb){ + var q = {_id:req.body.group}; + if (req.body.group!='habitrpg') q.members = {$in:[user._id]}; // make sure they're a member of the group + Group.findOne(q, cb); }, - function(_group, cb){ - if (!_group) return cb("Group." + req.body.group + " not found"); - group = _group; - cb(null); - } - ]; + save_chal: ['get_group', function(cb, results){ + var group = results.get_group, + prize = +req.body.prize; + if (!group) + return cb({code:404, err:"Group." + req.body.group + " not found"}); + //if (group.leaderOnly.challenges && group.leader !== user._id) + // return cb({code:401, err: "Only the group leader can create challenges"}); + // If they're adding a prize, do some validation + if (prize < 0) + return cb({code:401, err: 'Challenge prize must be >= 0'}); + if (req.body.group=='habitrpg' && prize < 1) + return cb({code:401, err: 'Prize must be at least 1 Gem for public challenges.'}); + if (prize > 0) { + var groupBalance = ((group.balance && group.leader==user._id) ? group.balance : 0); + var prizeCost = prize/4; // I really should have stored user.balance as gems rather than dollars... stupid... + if (prizeCost > user.balance + groupBalance) + return cb("You can't afford this prize. Purchase more gems or lower the prize amount.") - // If they're adding a prize, do some validation - if (+req.body.prize < 0) return res.json(401, {err: 'Challenge prize must be >= 0'}); - if (req.body.group=='habitrpg' && +req.body.prize < 1) return res.json(401, {err: 'Prize must be at least 1 Gem for public challenges.'}); - if (+req.body.prize > 0) { - waterfall.push(function(cb){ - var groupBalance = ((group.balance && group.leader==user._id) ? group.balance : 0); - var prizeCost = req.body.prize/4; // I really should have stored user.balance as gems rather than dollars... stupid... - if (prizeCost > user.balance + groupBalance) - return cb("You can't afford this prize. Purchase more gems or lower the prize amount.") - - if (groupBalance >= prizeCost) { - // Group pays for all of prize - group.balance -= prizeCost; - } else if (groupBalance > 0) { - // User pays remainder of prize cost after group - var remainder = prizeCost - group.balance; - group.balance = 0; - user.balance -= remainder; - } else { - // User pays for all of prize - user.balance -= prizeCost; - } - cb(null); - }); - } - - waterfall = waterfall.concat([ - function(cb) { // if we're dealing with prize above, arguemnts will be `group, numRows, cb` - else `cb` + if (groupBalance >= prizeCost) { + // Group pays for all of prize + group.balance -= prizeCost; + } else if (groupBalance > 0) { + // User pays remainder of prize cost after group + var remainder = prizeCost - group.balance; + group.balance = 0; + user.balance -= remainder; + } else { + // User pays for all of prize + user.balance -= prizeCost; + } + } req.body.leader = user._id; req.body.official = user.contributor.admin && req.body.official; var chal = new Challenge(req.body); // FIXME sanitize chal.members.push(user._id); - chal.save(cb) - }, - function(_chal, num, cb){ - chal = _chal; - group.challenges.push(chal._id); - group.save(cb); - }, - function(_group, num, cb) { + chal.save(cb); + }], + save_group: ['save_chal', function(cb, results){ + results.get_group.challenges.push(results.save_chal[0]._id); + results.get_group.save(cb); + }], + sync_user: ['save_group', function(cb, results){ // Auto-join creator to challenge (see members.push above) - chal.syncToUser(user, cb); - } - ]); - async.waterfall(waterfall, function(err){ - if (err) return next(err); - res.json(chal); - user = group = chal = null; - }); + results.save_chal[0].syncToUser(user, cb); + }] + }, function(err, results){ + if (err) return err.code? res.json(err.code, err) : next(err); + return res.json(results.save_chal[0]); + user = null; + }) } // UPDATE From d70de5972e1a6b3a6674ba105df50189b9783b1c Mon Sep 17 00:00:00 2001 From: thehindenburg Date: Thu, 11 Dec 2014 22:28:07 -0800 Subject: [PATCH 11/56] added the image to the end screen and made adjustments to the line --- views/shared/modals/quests.jade | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/views/shared/modals/quests.jade b/views/shared/modals/quests.jade index ccd4d39850..21abc8c095 100644 --- a/views/shared/modals/quests.jade +++ b/views/shared/modals/quests.jade @@ -5,7 +5,8 @@ script(type='text/ng-template', id='modals/questCompleted.html') h4 "{{::Content.quests[user.party.quest.completed].text()}}" =env.t('completed') .modal-body - p(ng-bind-html='::Content.quests[user.party.quest.completed].completion()') + .pull-right(class='quest_{{user.party.quest.completed}}') + p(ng-bind-html='::Content.quests[user.party.quest.completed].completion()' style='min-height: 80px') quest-rewards(key='{{user.party.quest.completed}}', header=env.t('youReceived')) .modal-footer button.btn.btn-primary(ng-click='set({"party.quest.completed":""}); $close()')=env.t('ok') From 7356e0bafeb3843d6c457c654abc7a68f999cf44 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 12 Dec 2014 12:11:26 +0100 Subject: [PATCH 12/56] feat(loggly): add loggly --- package.json | 1 + src/logging.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/package.json b/package.json index 061d4a227e..6f14a60e51 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "jade": "~1.7.0", "js2xmlparser": "~0.1.2", "lodash": "~2.4.1", + "loggly": "~1.0.8", "method-override": "~2.2.0", "moment": "~2.8.3", "mongoose": "~3.8.17", diff --git a/src/logging.js b/src/logging.js index 5e1449db3d..07e795e137 100644 --- a/src/logging.js +++ b/src/logging.js @@ -3,6 +3,19 @@ var winston = require('winston'); require('winston-mail').Mail; require('winston-newrelic'); +var loggly = require('loggly').createClient({ + token: nconf.get('LOGGLY:token'), + subdomain: nconf.get('LOGGLY:subdomain'), + auth: { + username: nconf.get('LOGGLY:username'), + password: nconf.get('LOGGLY:password') + }, + // + // Optional: Tag to send with EVERY log message + // + tags: ['heroku'] +}); + var logger; if (logger == null) { @@ -49,3 +62,8 @@ module.exports.error = function(/* variable args */) { if (logger) logger.error.apply(logger, arguments); }; + +module.exports.loggly = function(/* variable args */){ + if(loggly) + loggly.log(loggly, arguments); +}; From af1ff6d74059e12683603ca89a55e5a12ff1a9e4 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 12 Dec 2014 12:17:48 +0100 Subject: [PATCH 13/56] feat(loggly): add test log, use only in production and add sample config --- config.json.example | 8 +++++++- src/logging.js | 26 ++++++++++++++------------ src/server.js | 2 ++ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/config.json.example b/config.json.example index caedcf3ae9..f50242f74c 100644 --- a/config.json.example +++ b/config.json.example @@ -42,5 +42,11 @@ "client_id":"client_id", "client_secret":"client_secret" }, - "IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/" + "IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/", + "LOGGLY": { + "subdomain": "subdomain", + "token": "token", + "username": "username", + "password": "password" + } } diff --git a/src/logging.js b/src/logging.js index 07e795e137..9cfe8cfbd8 100644 --- a/src/logging.js +++ b/src/logging.js @@ -3,18 +3,20 @@ var winston = require('winston'); require('winston-mail').Mail; require('winston-newrelic'); -var loggly = require('loggly').createClient({ - token: nconf.get('LOGGLY:token'), - subdomain: nconf.get('LOGGLY:subdomain'), - auth: { - username: nconf.get('LOGGLY:username'), - password: nconf.get('LOGGLY:password') - }, - // - // Optional: Tag to send with EVERY log message - // - tags: ['heroku'] -}); +if (nconf.get('NODE_ENV') == 'production'){ + var loggly = require('loggly').createClient({ + token: nconf.get('LOGGLY:token'), + subdomain: nconf.get('LOGGLY:subdomain'), + auth: { + username: nconf.get('LOGGLY:username'), + password: nconf.get('LOGGLY:password') + }, + // + // Optional: Tag to send with EVERY log message + // + tags: ['heroku'] + }); +} var logger; diff --git a/src/server.js b/src/server.js index 8e551f7891..bd0e1310b0 100644 --- a/src/server.js +++ b/src/server.js @@ -49,6 +49,8 @@ if (cluster.isMaster && (isDev || isProd)) { }); autoinc.init(db); + logging.loggly('test from beta'); + // load schemas & models require('./models/challenge'); require('./models/group'); From b10d4606f5bf728b02e65c2ba892669d0144bf93 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 12 Dec 2014 13:09:21 +0100 Subject: [PATCH 14/56] fix(loggly): typo and remove testing --- src/logging.js | 2 +- src/server.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/logging.js b/src/logging.js index 9cfe8cfbd8..476a76d41b 100644 --- a/src/logging.js +++ b/src/logging.js @@ -67,5 +67,5 @@ module.exports.error = function(/* variable args */) { module.exports.loggly = function(/* variable args */){ if(loggly) - loggly.log(loggly, arguments); + loggly.log.apply(loggly, arguments); }; diff --git a/src/server.js b/src/server.js index bd0e1310b0..8e551f7891 100644 --- a/src/server.js +++ b/src/server.js @@ -49,8 +49,6 @@ if (cluster.isMaster && (isDev || isProd)) { }); autoinc.init(db); - logging.loggly('test from beta'); - // load schemas & models require('./models/challenge'); require('./models/group'); From 375253fb847355c68c190d6dabddd7ceb4b17ce2 Mon Sep 17 00:00:00 2001 From: thehindenburg Date: Fri, 12 Dec 2014 09:38:13 -0800 Subject: [PATCH 15/56] changing the reward text to clear the image --- views/shared/modals/quest-rewards.jade | 2 +- views/shared/modals/quests.jade | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/views/shared/modals/quest-rewards.jade b/views/shared/modals/quest-rewards.jade index ffa90622ab..acddcbf53f 100644 --- a/views/shared/modals/quest-rewards.jade +++ b/views/shared/modals/quest-rewards.jade @@ -1,5 +1,5 @@ script(id='partials/options.social.party.quest-rewards.html', type='text/ng-template') - hr + hr(style='clear:both') h5 {{header}} table.table.table-striped tr(ng-repeat='drop in quest.drop.items') diff --git a/views/shared/modals/quests.jade b/views/shared/modals/quests.jade index 21abc8c095..6ff8b9cd6a 100644 --- a/views/shared/modals/quests.jade +++ b/views/shared/modals/quests.jade @@ -6,7 +6,7 @@ script(type='text/ng-template', id='modals/questCompleted.html') =env.t('completed') .modal-body .pull-right(class='quest_{{user.party.quest.completed}}') - p(ng-bind-html='::Content.quests[user.party.quest.completed].completion()' style='min-height: 80px') + p(ng-bind-html='::Content.quests[user.party.quest.completed].completion()') quest-rewards(key='{{user.party.quest.completed}}', header=env.t('youReceived')) .modal-footer button.btn.btn-primary(ng-click='set({"party.quest.completed":""}); $close()')=env.t('ok') From 0943a229ec5cbb976e204ec7ec4641b138aea39b Mon Sep 17 00:00:00 2001 From: benmanley Date: Fri, 12 Dec 2014 17:38:17 +0000 Subject: [PATCH 16/56] fix(avatar.styl): improve label layout Fixes #4349 * Tweak position of labels to align better with background border. * Increase z-index of share button to prevent it being obscured by avatar. * Move share button to bottom left corner to prevent it obscuring avatar name. --- public/css/avatar.styl | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/public/css/avatar.styl b/public/css/avatar.styl index f5132b070f..ea00c40e02 100644 --- a/public/css/avatar.styl +++ b/public/css/avatar.styl @@ -31,16 +31,17 @@ future re: pets and whatnot, this is just temporary. position: absolute @extend $hrpg-label .avatar-level - bottom: 2px - right: 2px + bottom: 4px + right: 4px // when a buff/rebirth exists, add space between the icon and the level .glyphicon-circle-arrow-up padding-right: 4px .glyphicon-plus-sign padding-left: 4px .avatar-name - top: 2px - left: 2px + max-width: 132px + top: 4px + left: 4px opacity: 0 // Hide username by default transition: opacity 0.2s ease-out @@ -83,8 +84,9 @@ future re: pets and whatnot, this is just temporary. width: 50px !important .addthis_native_toolbox position: absolute - top:2px - right:2px + bottom:4px + left:4px + z-index: 2 opacity:0 &:hover .addthis_native_toolbox opacity:1 From 45d30ffef56cdec74849448195e9cca02af58d7d Mon Sep 17 00:00:00 2001 From: benmanley Date: Fri, 12 Dec 2014 17:41:26 +0000 Subject: [PATCH 17/56] fix(footerCtrl.js): disable addThis hover Activate addThis dropdown on click/tap not on hover. --- public/js/controllers/footerCtrl.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/js/controllers/footerCtrl.js b/public/js/controllers/footerCtrl.js index 9017602aec..4a42b10952 100644 --- a/public/js/controllers/footerCtrl.js +++ b/public/js/controllers/footerCtrl.js @@ -42,6 +42,7 @@ //$.getScript("//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-5016f6cc44ad68a4"); //FIXME why isn't this working when here? instead it's now in var addthisServices = 'facebook,twitter,googleplus,tumblr,'+window.env.BASE_URL.replace('https://','').replace('http://',''); window.addthis_config = { + ui_click: true, services_custom:{ name: "Download", url: window.env.BASE_URL+"/export/avatar-"+User.user._id+".png", From 2db80c684156b1550ee6078ccf07cbb7ffce47c4 Mon Sep 17 00:00:00 2001 From: thehindenburg Date: Fri, 12 Dec 2014 10:26:55 -0800 Subject: [PATCH 18/56] changed to style sheet --- public/css/quests.styl | 6 +++++- views/shared/modals/quest-rewards.jade | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/public/css/quests.styl b/public/css/quests.styl index fe7209f4e9..d885e32372 100644 --- a/public/css/quests.styl +++ b/public/css/quests.styl @@ -1,3 +1,7 @@ .quest_collected_true color: #ccc - text-decoration:line-through \ No newline at end of file + text-decoration:line-through + +quest-rewards + hr + clear:both \ No newline at end of file diff --git a/views/shared/modals/quest-rewards.jade b/views/shared/modals/quest-rewards.jade index acddcbf53f..ffa90622ab 100644 --- a/views/shared/modals/quest-rewards.jade +++ b/views/shared/modals/quest-rewards.jade @@ -1,5 +1,5 @@ script(id='partials/options.social.party.quest-rewards.html', type='text/ng-template') - hr(style='clear:both') + hr h5 {{header}} table.table.table-striped tr(ng-repeat='drop in quest.drop.items') From e1ea1a24c80674ab0aea0c60b65383db38c0c02e Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Fri, 12 Dec 2014 11:35:51 -0700 Subject: [PATCH 19/56] feat(challenges): optional checkbox on group create/edit: restrict challenge-creation to leader. Commented-out code for similar setup for member invites, shouldn't be hard --- src/controllers/challenges.js | 4 ++-- src/controllers/groups.js | 2 +- src/models/group.js | 5 ++++- views/options/social/create-group.jade | 5 +++++ views/options/social/group.jade | 5 +++++ 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/controllers/challenges.js b/src/controllers/challenges.js index d09decf572..71e1d82d33 100644 --- a/src/controllers/challenges.js +++ b/src/controllers/challenges.js @@ -159,8 +159,8 @@ api.create = function(req, res, next){ prize = +req.body.prize; if (!group) return cb({code:404, err:"Group." + req.body.group + " not found"}); - //if (group.leaderOnly.challenges && group.leader !== user._id) - // return cb({code:401, err: "Only the group leader can create challenges"}); + if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id) + return cb({code:401, err: "Only the group leader can create challenges"}); // If they're adding a prize, do some validation if (prize < 0) return cb({code:401, err: 'Challenge prize must be >= 0'}); diff --git a/src/controllers/groups.js b/src/controllers/groups.js index 50272cdab2..1d365d7d8b 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -187,7 +187,7 @@ api.update = function(req, res, next) { if(group.leader !== user._id) return res.json(401, {err: "Only the group leader can update the group!"}); - 'name description logo logo leaderMessage leader'.split(' ').forEach(function(attr){ + 'name description logo logo leaderMessage leader leaderOnly'.split(' ').forEach(function(attr){ group[attr] = req.body[attr]; }); diff --git a/src/models/group.js b/src/models/group.js index 1bc275afe4..85653670dc 100644 --- a/src/models/group.js +++ b/src/models/group.js @@ -26,7 +26,10 @@ var GroupSchema = new Schema({ # id: String # }] */ - + leaderOnly: { // restrict group actions to leader (members can't do them) + challenges: {type:Boolean, 'default':false}, + //invites: {type:Boolean, 'default':false} + }, memberCount: {type: Number, 'default': 0}, challengeCount: {type: Number, 'default': 0}, balance: Number, diff --git a/views/options/social/create-group.jade b/views/options/social/create-group.jade index b2211862c8..6c0c4c20e8 100644 --- a/views/options/social/create-group.jade +++ b/views/options/social/create-group.jade @@ -19,5 +19,10 @@ form.col-md-12.form-horizontal(ng-submit='create(newGroup)') span.gem-cost= '4 ' + env.t('gems') p small=env.t('gemCost') + .form-group + .checkbox + label + input(type='checkbox', ng-model='newGroup.leaderOnly.challenges') + | Only group leader can create challenges .form-group(ng-show='type=="party"') input.btn.btn-default.form-control(type='submit', value=env.t('create')) diff --git a/views/options/social/group.jade b/views/options/social/group.jade index 1b1d7a6f84..a3c5e257d8 100644 --- a/views/options/social/group.jade +++ b/views/options/social/group.jade @@ -33,6 +33,11 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter .form-group label=env.t('logoUrl') input.form-control(type='url', placeholder=env.t('logoUrl'), ng-model='group.logo') + .form-group + .checkbox + label + input(type='checkbox', ng-model='group.leaderOnly.challenges') + | Only group leader can create challenges h4=env.t('assignLeader') select#group-leader-selection(ng-model='group._newLeader', ng-options='member.profile.name for member in group.members') From 670542f3ce002d451a57605bf28d38c1b9d089c4 Mon Sep 17 00:00:00 2001 From: thehindenburg Date: Fri, 12 Dec 2014 10:44:06 -0800 Subject: [PATCH 20/56] cleaning up white space --- public/css/quests.styl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/css/quests.styl b/public/css/quests.styl index d885e32372..c74377dc71 100644 --- a/public/css/quests.styl +++ b/public/css/quests.styl @@ -3,5 +3,5 @@ text-decoration:line-through quest-rewards - hr - clear:both \ No newline at end of file + hr + clear:both \ No newline at end of file From 7c6d8d4dc23a552653f8e9f5a84effcfc2c8b4f5 Mon Sep 17 00:00:00 2001 From: thehindenburg Date: Fri, 12 Dec 2014 11:02:55 -0800 Subject: [PATCH 21/56] Update quests.styl further white space cleanup --- public/css/quests.styl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/css/quests.styl b/public/css/quests.styl index c74377dc71..3f5fa8a79b 100644 --- a/public/css/quests.styl +++ b/public/css/quests.styl @@ -4,4 +4,4 @@ quest-rewards hr - clear:both \ No newline at end of file + clear:both From 8935538b5351d8da9b0aee454b1444b63480932c Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 12 Dec 2014 20:05:37 +0100 Subject: [PATCH 22/56] feat(loggly): enable json loggin and start loggin errors during cron --- src/controllers/user.js | 65 +++++++++++++++++++++++------------------ src/logging.js | 3 +- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index bceb3eb677..68cc7b9ccb 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -257,37 +257,44 @@ api.update = function(req, res, next) { }; api.cron = function(req, res, next) { - var user = res.locals.user, - progress = user.fns.cron(), - ranCron = user.isModified(), - quest = shared.content.quests[user.party.quest.key]; + try{ + var user = res.locals.user, + progress = user.fns.cron(), + ranCron = user.isModified(), + quest = shared.content.quests[user.party.quest.key]; + if (ranCron) res.locals.wasModified = true; + if (!ranCron) return next(null,user); + Group.tavernBoss(user,progress); + if (!quest) return user.save(next); - if (ranCron) res.locals.wasModified = true; - if (!ranCron) return next(null,user); - Group.tavernBoss(user,progress); - if (!quest) return user.save(next); - - // If user is on a quest, roll for boss & player, or handle collections - // FIXME this saves user, runs db updates, loads user. Is there a better way to handle this? - async.waterfall([ - function(cb){ - user.save(cb); // make sure to save the cron effects - }, - function(saved, count, cb){ - var type = quest.boss ? 'boss' : 'collect'; - Group[type+'Quest'](user,progress,cb); - }, - function(){ - var cb = arguments[arguments.length-1]; - // User has been updated in boss-grapple, reload - User.findById(user._id, cb); - } - ], function(err, saved) { - res.locals.user = saved; - next(err,saved); - user = progress = quest = null; - }); + // If user is on a quest, roll for boss & player, or handle collections + // FIXME this saves user, runs db updates, loads user. Is there a better way to handle this? + async.waterfall([ + function(cb){ + user.save(cb); // make sure to save the cron effects + }, + function(saved, count, cb){ + var type = quest.boss ? 'boss' : 'collect'; + Group[type+'Quest'](user,progress,cb); + }, + function(){ + var cb = arguments[arguments.length-1]; + // User has been updated in boss-grapple, reload + User.findById(user._id, cb); + } + ], function(err, saved) { + if(err) logging.loggly({error: e, stack: e.stack}) + res.locals.user = saved; + next(err,saved); + user = progress = quest = null; + }); + }catch(e){ + logging.loggly({ + error: e.stack || e + }); + throw e; + } }; diff --git a/src/logging.js b/src/logging.js index 476a76d41b..63ab51ade9 100644 --- a/src/logging.js +++ b/src/logging.js @@ -14,7 +14,8 @@ if (nconf.get('NODE_ENV') == 'production'){ // // Optional: Tag to send with EVERY log message // - tags: ['heroku'] + tags: ['heroku'], + json: true }); } From e98243e3d80dfd727a52ca373b8ad45f283aad3a Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Fri, 12 Dec 2014 11:57:55 -0700 Subject: [PATCH 23/56] refactor(env): small cleanup --- src/middleware.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/middleware.js b/src/middleware.js index ba5ed01f07..2efe9b470f 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -173,12 +173,9 @@ module.exports.locals = function(req, res, next) { language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined); var tavern = require('./models/group').tavern; - res.locals.habitrpg = { - NODE_ENV: nconf.get('NODE_ENV'), - BASE_URL: nconf.get('BASE_URL'), - GA_ID: nconf.get("GA_ID"), + 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')), - STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY'), getManifestFiles: getManifestFiles, getBuildUrl: getBuildUrl, avalaibleLanguages: i18n.avalaibleLanguages, @@ -193,11 +190,9 @@ module.exports.locals = function(req, res, next) { siteVersion: siteVersion, Content: shared.content, mods: require('./models/user').mods, - FACEBOOK_KEY: nconf.get('FACEBOOK_KEY'), - tavern: tavern, // for world boss worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {} - }; + }); // Put query-string party invitations into session to be handled later try{ From 9d6caaa138d31d4c784737150ea313d68c69b745 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Fri, 12 Dec 2014 16:18:33 -0700 Subject: [PATCH 24/56] feat(iap): ignore keys/ directory. upload any keys/iap-sandbox iap-live etc on a private branch --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1717063857..5b5e6f87bd 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ test/*.js test/*.map public/docs *.sublime-workspace +keys From dac2fc682dfedbd9373b9d2695a857b1cf7afebb Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Sat, 13 Dec 2014 14:48:14 +0100 Subject: [PATCH 25/56] feat(loggly): change tag based on base_url, logs internal server errors --- src/controllers/user.js | 5 +++-- src/logging.js | 6 +++--- src/middleware.js | 7 +++++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index 68cc7b9ccb..e24800980a 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -284,14 +284,15 @@ api.cron = function(req, res, next) { User.findById(user._id, cb); } ], function(err, saved) { - if(err) logging.loggly({error: e, stack: e.stack}) + if(err) logging.loggly({error: "Cron caught", stack: e.stack || e}) res.locals.user = saved; next(err,saved); user = progress = quest = null; }); }catch(e){ logging.loggly({ - error: e.stack || e + error: "Cron uncaught", + stack: e.stack || e }); throw e; } diff --git a/src/logging.js b/src/logging.js index 63ab51ade9..441af207ee 100644 --- a/src/logging.js +++ b/src/logging.js @@ -3,6 +3,8 @@ var winston = require('winston'); require('winston-mail').Mail; require('winston-newrelic'); +var logger, loggly; + if (nconf.get('NODE_ENV') == 'production'){ var loggly = require('loggly').createClient({ token: nconf.get('LOGGLY:token'), @@ -14,13 +16,11 @@ if (nconf.get('NODE_ENV') == 'production'){ // // Optional: Tag to send with EVERY log message // - tags: ['heroku'], + tags: [('heroku-'+nconf.get('BASE_URL'))], json: true }); } -var logger; - if (logger == null) { logger = new (winston.Logger)({}); if (nconf.get('NODE_ENV') == 'production') { diff --git a/src/middleware.js b/src/middleware.js index 2efe9b470f..56e72bda7e 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -75,6 +75,13 @@ module.exports.errorHandler = function(err, req, res, next) { "\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'] + ' | ' + req.headers['x-api-key']), + 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); From 012e579cfe2f5a2003bc0f50ce9421e2b5f51041 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Sat, 13 Dec 2014 14:57:05 +0100 Subject: [PATCH 26/56] fix(loggly): typo --- src/controllers/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index e24800980a..7bf164cd73 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -284,7 +284,7 @@ api.cron = function(req, res, next) { User.findById(user._id, cb); } ], function(err, saved) { - if(err) logging.loggly({error: "Cron caught", stack: e.stack || e}) + if(err) logging.loggly({error: "Cron caught", stack: err.stack || err}); res.locals.user = saved; next(err,saved); user = progress = quest = null; From 61ae5c74209e7399a817b48d7f6d4afc4cbed4fc Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Sat, 13 Dec 2014 15:01:38 +0100 Subject: [PATCH 27/56] fix(loggly): enable only manually --- config.json.example | 1 + src/logging.js | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/config.json.example b/config.json.example index f50242f74c..74e2f0822c 100644 --- a/config.json.example +++ b/config.json.example @@ -44,6 +44,7 @@ }, "IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/", "LOGGLY": { + "enabled": false, "subdomain": "subdomain", "token": "token", "username": "username", diff --git a/src/logging.js b/src/logging.js index 441af207ee..5c0d973812 100644 --- a/src/logging.js +++ b/src/logging.js @@ -5,8 +5,8 @@ require('winston-newrelic'); var logger, loggly; -if (nconf.get('NODE_ENV') == 'production'){ - var loggly = require('loggly').createClient({ +if (nconf.get('LOGGLY:enabled')){ + loggly = require('loggly').createClient({ token: nconf.get('LOGGLY:token'), subdomain: nconf.get('LOGGLY:subdomain'), auth: { @@ -67,6 +67,6 @@ module.exports.error = function(/* variable args */) { }; module.exports.loggly = function(/* variable args */){ - if(loggly) - loggly.log.apply(loggly, arguments); + if (loggly) + loggly.log.apply(loggly, arguments); }; From 4adf95feb644e35eb2c55fe53d0cb2a0248b10bf Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sat, 13 Dec 2014 12:05:17 -0600 Subject: [PATCH 28/56] fix(purchases): Hide "2/locked" as appropriate --- views/options/profile.jade | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/views/options/profile.jade b/views/options/profile.jade index 6e212061dd..350079b9a5 100644 --- a/views/options/profile.jade +++ b/views/options/profile.jade @@ -15,8 +15,9 @@ mixin customizeProfile(mobile) if ~['limited','seasonal'].indexOf(status) .label.label-info.pull-right.hint(popover=limited, popover-title=env.t(status+'Edition'), popover-placement='right', popover-trigger='mouseenter')=env.t(status+'Edition') menu(label=env.t(title)) - +gemCost(2) - button.btn.btn-xs(ng-hide='#{status=="disabled"} || #{showPath("user.purchased."+path, colors, "||")}', ng-click='#{unlockPath(path, colors)}')!= env.t('unlockSet',{cost:5}) + ' ' + span(ng-hide='#{status=="disabled"} || #{showPath("user.purchased."+path, colors, "&&")}') + +gemCost(2) + button.btn.btn-xs(ng-click='#{unlockPath(path, colors)}')!= env.t('unlockSet',{cost:5}) + ' ' each color in colors button.customize-option(type='button', class='#{path=="skin" ? "skin_"+color : "customize-option hair hair_bangs_1_"+color}', ng-class='{locked: !user.purchased.#{path}["#{color}"]}', ng-if='#{status!="disabled"} || user.purchased.#{path}["#{color}"]', ng-click='unlock("#{path}.#{color}")') @@ -38,8 +39,9 @@ mixin customizeProfile(mobile) menu(label=env.t('specialShirts')) - var specialShirts = ['convict', 'cross', 'fire', 'horizon', 'ocean', 'purple', 'rainbow', 'redblue', 'thunder', 'tropical', 'zombie'] - +gemCost(2) - button.btn.btn-xs(ng-hide='#{showPath("user.purchased.shirt", specialShirts, "&&")}', ng-click='#{unlockPath("shirt",specialShirts)}')!= env.t('unlockSet',{cost:5}) + ' ' + span(ng-hide='#{showPath("user.purchased.shirt", specialShirts, "&&")}') + +gemCost(2) + button.btn.btn-xs(ng-click='#{unlockPath("shirt",specialShirts)}')!= env.t('unlockSet',{cost:5}) + ' ' each shirt in specialShirts button.customize-option(type='button', class='{{user.preferences.size}}_shirt_'+shirt, ng-class='{locked: !user.purchased.shirt.'+shirt+'}', ng-click='unlock("shirt.'+shirt+'")') @@ -80,8 +82,9 @@ mixin customizeProfile(mobile) // Purchasable hairstyles menu(label=env.t('hairSet1')) - var colors = [2,4,5,6,7,8] - +gemCost(2) - button.btn.btn-xs(ng-hide='#{showPath("user.purchased.hair", colors, "&&")}', ng-click='#{unlockPath("hair.base",colors)}')!= env.t('unlockSet',{cost:5}) + ' ' + span(ng-hide='#{showPath("user.purchased.hair", colors, "&&")}') + +gemCost(2) + button.btn.btn-xs(ng-click='#{unlockPath("hair.base",colors)}')!= env.t('unlockSet',{cost:5}) + ' ' each num in colors button(class='hair_base_#{num}_{{user.preferences.hair.color}} customize-option', type='button', ng-class='{locked: !user.purchased.hair.base["#{num}"]}', ng-click='unlock("hair.base.#{num}")') @@ -94,8 +97,9 @@ mixin customizeProfile(mobile) li.customize-menu h5=env.t('bodyFacialHair') - +gemCost(2) - button.btn.btn-xs(ng-hide='user.purchased.hair.mustache["1"] && user.purchased.hair.mustache["2"] && user.purchased.hair.beard["1"] && user.purchased.hair.beard["2"] && user.purchased.hair.beard["3"]', ng-click='unlock("hair.mustache.1,hair.mustache.2,hair.beard.1,hair.beard.2,hair.beard.3")')!= env.t('unlockSet',{cost:5}) + ' ' + span(ng-hide='user.purchased.hair.mustache["1"] && user.purchased.hair.mustache["2"] && user.purchased.hair.beard["1"] && user.purchased.hair.beard["2"] && user.purchased.hair.beard["3"]') + +gemCost(2) + button.btn.btn-xs(ng-click='unlock("hair.mustache.1,hair.mustache.2,hair.beard.1,hair.beard.2,hair.beard.3")')!= env.t('unlockSet',{cost:5}) + ' ' // Beard menu(label=env.t('beard')) @@ -249,9 +253,9 @@ mixin backgrounds(mobile) - var k = bgsKeys[i], bgs = env.Content.backgrounds[k]; li.customize-menu menu(label=env.t(k)) - +gemCost(7) - //-button.btn.btn-xs(ng-hide="ownsSet('background',Content.backgrounds['#{k}'])",ng-click="unlock(setKeys('background',Content.backgrounds['#{k}']))")!= env.t('unlockSet',{cost:15}) + ' ' - button.btn.btn-xs(ng-hide="ownsSet('background',#{JSON.stringify(bgs)})",ng-click="unlock(setKeys('background',#{JSON.stringify(bgs)}))")!= env.t('unlockSet',{cost:15}) + ' ' + span(ng-hide="ownsSet('background',#{JSON.stringify(bgs)})") + +gemCost(7) + button.btn.btn-xs(ng-click="unlock(setKeys('background',#{JSON.stringify(bgs)}))")!= env.t('unlockSet',{cost:15}) + ' ' each bg,k in bgs button.customize-option(type='button', class='background_#{k}', ng-class="user.purchased.background.#{k} ? 'background-unlocked' : 'background-locked'", ng-click='unlock("background.#{k}")', popover-title=bg.text(), popover=bg.notes(),popover-trigger='mouseenter') i.glyphicon.glyphicon-lock(ng-if="!user.purchased.background.#{k}") From 4ded747b8811d779d64f957bd937fd566add89b3 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sat, 13 Dec 2014 15:53:39 -0600 Subject: [PATCH 29/56] fix(purchases): Correct hair path Fixes https://github.com/HabitRPG/habitrpg/issues/4312. --- views/options/profile.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/options/profile.jade b/views/options/profile.jade index 350079b9a5..2427b9cd6a 100644 --- a/views/options/profile.jade +++ b/views/options/profile.jade @@ -82,7 +82,7 @@ mixin customizeProfile(mobile) // Purchasable hairstyles menu(label=env.t('hairSet1')) - var colors = [2,4,5,6,7,8] - span(ng-hide='#{showPath("user.purchased.hair", colors, "&&")}') + span(ng-hide='#{showPath("user.purchased.hair.base", colors, "&&")}') +gemCost(2) button.btn.btn-xs(ng-click='#{unlockPath("hair.base",colors)}')!= env.t('unlockSet',{cost:5}) + ' ' each num in colors From e4df3435e13690288c920236c039b70988bc24cf Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sat, 13 Dec 2014 16:07:58 -0600 Subject: [PATCH 30/56] fix(avatar): Correct "Facial Hair" text Adjusts the text style of "Facial Hair" header in Avatar shop to follow the same scheme as Hair. Before, the text was less header-like than elements under it. --- views/options/profile.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/options/profile.jade b/views/options/profile.jade index 2427b9cd6a..cf8fef0e25 100644 --- a/views/options/profile.jade +++ b/views/options/profile.jade @@ -96,7 +96,7 @@ mixin customizeProfile(mobile) button(class='hair_flower_#{num} customize-option', type='button', ng-click='set({"preferences.hair.flower":#{num}})') li.customize-menu - h5=env.t('bodyFacialHair') + menu(label=env.t('bodyFacialHair')) span(ng-hide='user.purchased.hair.mustache["1"] && user.purchased.hair.mustache["2"] && user.purchased.hair.beard["1"] && user.purchased.hair.beard["2"] && user.purchased.hair.beard["3"]') +gemCost(2) button.btn.btn-xs(ng-click='unlock("hair.mustache.1,hair.mustache.2,hair.beard.1,hair.beard.2,hair.beard.3")')!= env.t('unlockSet',{cost:5}) + ' ' From 41e1f4c75f0a1d69e90426045d9900c80a3e8b97 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 14 Dec 2014 08:13:50 -0600 Subject: [PATCH 31/56] Conditionally allow completed quest image to be centered if no completion text is present --- public/css/helpers.styl | 8 +++++++- views/shared/modals/quests.jade | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/public/css/helpers.styl b/public/css/helpers.styl index 52d245a3fb..5dd3e778f0 100644 --- a/public/css/helpers.styl +++ b/public/css/helpers.styl @@ -32,4 +32,10 @@ visibility: hidden .transparent - opacity: 0.5 \ No newline at end of file + opacity: 0.5 + +// Allow block element to be centered +.col-centered + margin-left: auto + margin-right: auto + diff --git a/views/shared/modals/quests.jade b/views/shared/modals/quests.jade index 6ff8b9cd6a..ad53c0cd1f 100644 --- a/views/shared/modals/quests.jade +++ b/views/shared/modals/quests.jade @@ -5,7 +5,7 @@ script(type='text/ng-template', id='modals/questCompleted.html') h4 "{{::Content.quests[user.party.quest.completed].text()}}" =env.t('completed') .modal-body - .pull-right(class='quest_{{user.party.quest.completed}}') + div(ng-class='::Content.quests[user.party.quest.completed].completion() ? "pull-right" : "col-centered"', class='quest_{{user.party.quest.completed}}') p(ng-bind-html='::Content.quests[user.party.quest.completed].completion()') quest-rewards(key='{{user.party.quest.completed}}', header=env.t('youReceived')) .modal-footer From d0df9e46d64f72efb821d4253db75f4cd66876d8 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 14 Dec 2014 08:31:40 -0600 Subject: [PATCH 32/56] Added helper classes for floats at various screen sizes --- public/css/helpers.styl | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/public/css/helpers.styl b/public/css/helpers.styl index 5dd3e778f0..d9b54a3089 100644 --- a/public/css/helpers.styl +++ b/public/css/helpers.styl @@ -39,3 +39,26 @@ margin-left: auto margin-right: auto +// .pull-right & .pull-left for specific screen sizes +// Follows conventions for col-x-SIZE classes + +@media (min-width: 768px) + .pull-left-sm + float: left + + .pull-right-sm + float: right + +@media (min-width: 992px) + .pull-left-md + float: left + + .pull-right-md + float: right + +@media (min-width: 1200px) + .pull-left-lg + float: left + + .pull-right-lg + float: right From 396176f14f153bec5532dd20a323bb9d0c1ddcbf Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 14 Dec 2014 08:32:05 -0600 Subject: [PATCH 33/56] Quest completion screen floats to right on everything but xs --- views/shared/modals/quests.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/shared/modals/quests.jade b/views/shared/modals/quests.jade index ad53c0cd1f..6ccf0dd06f 100644 --- a/views/shared/modals/quests.jade +++ b/views/shared/modals/quests.jade @@ -5,7 +5,7 @@ script(type='text/ng-template', id='modals/questCompleted.html') h4 "{{::Content.quests[user.party.quest.completed].text()}}" =env.t('completed') .modal-body - div(ng-class='::Content.quests[user.party.quest.completed].completion() ? "pull-right" : "col-centered"', class='quest_{{user.party.quest.completed}}') + div(ng-class='::Content.quests[user.party.quest.completed].completion() ? "pull-right-sm" : ""', class='col-centered quest_{{user.party.quest.completed}}') p(ng-bind-html='::Content.quests[user.party.quest.completed].completion()') quest-rewards(key='{{user.party.quest.completed}}', header=env.t('youReceived')) .modal-footer From a961d6b342deed6fea4bd5fc3b862ed7d8a1447f Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Tue, 9 Dec 2014 07:00:24 -0600 Subject: [PATCH 34/56] Added wrapping rule for user tooltip in chat messages closes #4367 --- public/css/game-pane.styl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/css/game-pane.styl b/public/css/game-pane.styl index 1a002370ba..f5416d9cb2 100644 --- a/public/css/game-pane.styl +++ b/public/css/game-pane.styl @@ -37,6 +37,10 @@ markdown, span.time padding: 0 3px line-height: 1.618em + + .tooltip + white-space: normal + span.time white-space: nowrap From dde3f5ffef2a937a4ba8af3b6424be2bcff60592 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 14 Dec 2014 12:18:59 -0600 Subject: [PATCH 35/56] Adjusted tooltip wrapping so it would only affect long titles --- public/css/game-pane.styl | 3 +-- views/options/social/chat-message.jade | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/public/css/game-pane.styl b/public/css/game-pane.styl index f5416d9cb2..8ccc3c211d 100644 --- a/public/css/game-pane.styl +++ b/public/css/game-pane.styl @@ -38,9 +38,8 @@ padding: 0 3px line-height: 1.618em - .tooltip + .long-title .tooltip white-space: normal - span.time white-space: nowrap diff --git a/views/options/social/chat-message.jade b/views/options/social/chat-message.jade index dc332452ed..89ebc181e6 100644 --- a/views/options/social/chat-message.jade +++ b/views/options/social/chat-message.jade @@ -26,7 +26,7 @@ mixin chatMessages(inbox) |         a(ng-click='#{inbox? "user.ops.deletePM({params:{id:message.$key}})" : "deleteChatMessage(group, message)"}', ng-if='#{inbox ? "true" : ":: user.contributor.admin || message.uuid == user.id"}') span.glyphicon.glyphicon-trash(tooltip=env.t('delete')) - span.float-label + span.float-label(ng-class='::contribText(message.contributor, message.backer).length > 30 ? "long-title" : ""') a.label.label-default.chat-message(ng-if=':: message.user', ng-class='::userLevelStyleFromLevel(message.contributor.level, message.backer.npc, style)', ng-click='clickMember(message.uuid, true)') span.glyphicon.glyphicon-arrow-right(ng-if='::message.sent') |   From 35b02cfdd894a21f69175b81b5fefc74a0c35cda Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sun, 14 Dec 2014 12:59:45 -0600 Subject: [PATCH 36/56] fix(I18N): Make Time Travel translatable --- views/options/inventory/index.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/options/inventory/index.jade b/views/options/inventory/index.jade index af191f3de1..6ef2e4302b 100644 --- a/views/options/inventory/index.jade +++ b/views/options/inventory/index.jade @@ -17,7 +17,7 @@ script(type='text/ng-template', id='partials/options.inventory.html') =env.t('equipment') li.equipment-tab(ng-class="{ active: $state.includes('options.inventory.timetravelers') }") a(ui-sref='options.inventory.timetravelers') - | Time Travelers + =env.t('timeTravelers') .tab-content .tab-pane.active From a98a68597127f385c7729f25ff6f9f282ea20d0a Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sun, 14 Dec 2014 13:37:32 -0600 Subject: [PATCH 37/56] feat(seasonal): Seasonal Shop Initial commit of frontend for Seasonal Shop feature. --- views/options/inventory/index.jade | 3 +++ views/options/inventory/inventory.jade | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/views/options/inventory/index.jade b/views/options/inventory/index.jade index 6ef2e4302b..b2f0cfb3ec 100644 --- a/views/options/inventory/index.jade +++ b/views/options/inventory/index.jade @@ -18,6 +18,9 @@ script(type='text/ng-template', id='partials/options.inventory.html') li.equipment-tab(ng-class="{ active: $state.includes('options.inventory.timetravelers') }") a(ui-sref='options.inventory.timetravelers') =env.t('timeTravelers') + li.equipment-tab(ng-class="{ active: $state.includes('options.inventory.seasonalshop') }") + a(ui-sref='options.inventory.seasonalshop') + =env.t('seasonalShop') .tab-content .tab-pane.active diff --git a/views/options/inventory/inventory.jade b/views/options/inventory/inventory.jade index 3d872231a6..d0fc5ad7d2 100644 --- a/views/options/inventory/inventory.jade +++ b/views/options/inventory/inventory.jade @@ -19,6 +19,18 @@ script(type='text/ng-template', id='partials/options.inventory.equipment.html') div(ng-repeat='item in gear[klass]') button.customize-option(popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({params:{type:"costume", key:item.key}})', class='shop_{{::item.key}}', ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}') +script(type='text/ng-template', id='partials/options.inventory.seasonalshop.html') +.container-fluid + .stable.row + .col-md-2 + .seasonalshop_closed + .col-md-10 + .popover.static-popover.fade.right.in + .arrow + h3.popover-title!=env.t('seasonalShopClosedTitle') + .popover-content + p=env.t('seasonalShopClosedText', {linkStart: "", linkEnd: ""}) + script(type='text/ng-template', id='partials/options.inventory.timetravelers.html') .container-fluid .stable.row(ng-if='user.purchased.plan.consecutive.trinkets <= 0') From 506c396baf0ee708026644ef49a2c52ec676f620 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sun, 14 Dec 2014 13:53:00 -0600 Subject: [PATCH 38/56] fix(I18N): Translate TT in menu --- views/shared/header/menu.jade | 1 + 1 file changed, 1 insertion(+) diff --git a/views/shared/header/menu.jade b/views/shared/header/menu.jade index 486cdd95f3..f0a24e16c5 100644 --- a/views/shared/header/menu.jade +++ b/views/shared/header/menu.jade @@ -50,6 +50,7 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}') a(ui-sref='options.inventory.equipment')=env.t('equipment') li a(ui-sref='options.inventory.timetravelers') Time Travelers + a(ui-sref='options.inventory.timetravelers')=env.t('timeTravelers') ul.toolbar-submenu li a(target="_blank" ng-href='http://data.habitrpg.com?uuid={{user._id}}')=env.t('dataTool') From 29f693cd246c20f0a85c644442566657e7a8fd93 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sun, 14 Dec 2014 13:55:40 -0600 Subject: [PATCH 39/56] fix(I18N): Remove hardcoded text --- views/shared/header/menu.jade | 1 - 1 file changed, 1 deletion(-) diff --git a/views/shared/header/menu.jade b/views/shared/header/menu.jade index f0a24e16c5..d2586c99c5 100644 --- a/views/shared/header/menu.jade +++ b/views/shared/header/menu.jade @@ -49,7 +49,6 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}') li a(ui-sref='options.inventory.equipment')=env.t('equipment') li - a(ui-sref='options.inventory.timetravelers') Time Travelers a(ui-sref='options.inventory.timetravelers')=env.t('timeTravelers') ul.toolbar-submenu li From 7988dfcf824079bc9f6c5be32f13aa1cc58234ce Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sun, 14 Dec 2014 14:01:25 -0600 Subject: [PATCH 40/56] fix(seasonal): Corrections for Shop Fix Jade indenting, add state control, add menu option --- public/js/app.js | 4 ++++ views/options/inventory/inventory.jade | 20 ++++++++++---------- views/shared/header/menu.jade | 2 ++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index c12ba3aa08..8bf5c0c47e 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -185,6 +185,10 @@ window.habitrpg = angular.module('habitrpg', url: '/timetravelers', templateUrl: "partials/options.inventory.timetravelers.html" }) + .state('options.inventory.seasonalshop', { + url: '/seasonalshop', + templateUrl: "partials/options.inventory.seasonalshop.html" + }) // Options > Settings .state('options.settings', { diff --git a/views/options/inventory/inventory.jade b/views/options/inventory/inventory.jade index d0fc5ad7d2..947500f63a 100644 --- a/views/options/inventory/inventory.jade +++ b/views/options/inventory/inventory.jade @@ -20,16 +20,16 @@ script(type='text/ng-template', id='partials/options.inventory.equipment.html') button.customize-option(popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({params:{type:"costume", key:item.key}})', class='shop_{{::item.key}}', ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}') script(type='text/ng-template', id='partials/options.inventory.seasonalshop.html') -.container-fluid - .stable.row - .col-md-2 - .seasonalshop_closed - .col-md-10 - .popover.static-popover.fade.right.in - .arrow - h3.popover-title!=env.t('seasonalShopClosedTitle') - .popover-content - p=env.t('seasonalShopClosedText', {linkStart: "", linkEnd: ""}) + .container-fluid + .stable.row + .col-md-2 + .seasonalshop_closed + .col-md-10 + .popover.static-popover.fade.right.in + .arrow + h3.popover-title!=env.t('seasonalShopClosedTitle') + .popover-content + p=env.t('seasonalShopClosedText', {linkStart: "", linkEnd: ""}) script(type='text/ng-template', id='partials/options.inventory.timetravelers.html') .container-fluid diff --git a/views/shared/header/menu.jade b/views/shared/header/menu.jade index 486cdd95f3..c83fbe8e78 100644 --- a/views/shared/header/menu.jade +++ b/views/shared/header/menu.jade @@ -50,6 +50,8 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}') a(ui-sref='options.inventory.equipment')=env.t('equipment') li a(ui-sref='options.inventory.timetravelers') Time Travelers + li + a(ui-sref='options.inventory.seasonalshop')=env.t('seasonalshop') ul.toolbar-submenu li a(target="_blank" ng-href='http://data.habitrpg.com?uuid={{user._id}}')=env.t('dataTool') From 6dcbd329a50fc6182a36f8a0c2c0c89cb8ae0261 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sun, 14 Dec 2014 14:05:43 -0600 Subject: [PATCH 41/56] fix(seasonal): Typo in string resource --- views/shared/header/menu.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/shared/header/menu.jade b/views/shared/header/menu.jade index c83fbe8e78..0cdb1ea759 100644 --- a/views/shared/header/menu.jade +++ b/views/shared/header/menu.jade @@ -51,7 +51,7 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}') li a(ui-sref='options.inventory.timetravelers') Time Travelers li - a(ui-sref='options.inventory.seasonalshop')=env.t('seasonalshop') + a(ui-sref='options.inventory.seasonalshop')=env.t('seasonalShop') ul.toolbar-submenu li a(target="_blank" ng-href='http://data.habitrpg.com?uuid={{user._id}}')=env.t('dataTool') From 8cd1ea48dbc4eea07cd4822bd0743046cad00aba Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sun, 14 Dec 2014 14:10:39 -0600 Subject: [PATCH 42/56] fix(I18N): Remove more hardcoded text --- views/shared/header/menu.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/shared/header/menu.jade b/views/shared/header/menu.jade index d2586c99c5..c52c700c27 100644 --- a/views/shared/header/menu.jade +++ b/views/shared/header/menu.jade @@ -131,7 +131,7 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}') li a(ui-sref='options.inventory.equipment')=env.t('equipment') li - a(ui-sref='options.inventory.timetravelers') Time Travelers + a(ui-sref='options.inventory.timetravelers')=env.t('timeTravelers') li.toolbar-button-dropdown a(target="_blank" ng-href='http://data.habitrpg.com?uuid={{user._id}}') span=env.t('data') From 867f50b514566e0140576bba75abb706ce156a6d Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sun, 14 Dec 2014 14:12:20 -0600 Subject: [PATCH 43/56] fix(seasonal): Add menu item --- views/shared/header/menu.jade | 2 ++ 1 file changed, 2 insertions(+) diff --git a/views/shared/header/menu.jade b/views/shared/header/menu.jade index 0cdb1ea759..364db96d7e 100644 --- a/views/shared/header/menu.jade +++ b/views/shared/header/menu.jade @@ -134,6 +134,8 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}') a(ui-sref='options.inventory.equipment')=env.t('equipment') li a(ui-sref='options.inventory.timetravelers') Time Travelers + li + a(ui-sref='options.inventory.seasonalshop')=env.t('seasonalShop') li.toolbar-button-dropdown a(target="_blank" ng-href='http://data.habitrpg.com?uuid={{user._id}}') span=env.t('data') From 9f46e14cd6ca5ca222d958548a3d794a41662c7a Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sun, 14 Dec 2014 14:18:57 -0600 Subject: [PATCH 44/56] fix(seasonal): missing bang --- views/options/inventory/inventory.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/options/inventory/inventory.jade b/views/options/inventory/inventory.jade index 947500f63a..929919d94b 100644 --- a/views/options/inventory/inventory.jade +++ b/views/options/inventory/inventory.jade @@ -29,7 +29,7 @@ script(type='text/ng-template', id='partials/options.inventory.seasonalshop.html .arrow h3.popover-title!=env.t('seasonalShopClosedTitle') .popover-content - p=env.t('seasonalShopClosedText', {linkStart: "", linkEnd: ""}) + p!=env.t('seasonalShopClosedText', {linkStart: "", linkEnd: ""}) script(type='text/ng-template', id='partials/options.inventory.timetravelers.html') .container-fluid From d1b1a866ac6401e05e81fa8428420fac19efd1c8 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 14 Dec 2014 19:01:52 -0600 Subject: [PATCH 45/56] Fixed when routing. Closes #4371 --- public/js/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/app.js b/public/js/app.js index c12ba3aa08..387692dbaf 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -22,9 +22,9 @@ window.habitrpg = angular.module('habitrpg', // Setup default selected tabs .when('/options', '/options/profile/avatar') .when('/options/profile', '/options/profile/avatar') - .when('/options/groups', '/options/groups/tavern') .when('/options/groups/guilds', '/options/groups/guilds/public') .when('/options/groups/hall', '/options/groups/hall/heroes') + .when('/options/groups', '/options/groups/tavern') .when('/options/inventory', '/options/inventory/drops') .when('/options/settings', '/options/settings/settings') From 9cdbca8c2b5e6b352c463ae875b211004c8d894e Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 14 Dec 2014 20:05:01 -0600 Subject: [PATCH 46/56] Reverted routing for groups back --- public/js/app.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index 387692dbaf..ee390df22d 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -7,7 +7,6 @@ window.habitrpg = angular.module('habitrpg', // @see https://github.com/angular-ui/ui-router/issues/110 and https://github.com/HabitRPG/habitrpg/issues/1705 // temporary hack until they have a better solution - .value('$anchorScroll', angular.noop) .constant("API_URL", "") .constant("STORAGE_USER_ID", 'habitrpg-user') @@ -22,9 +21,9 @@ window.habitrpg = angular.module('habitrpg', // Setup default selected tabs .when('/options', '/options/profile/avatar') .when('/options/profile', '/options/profile/avatar') + .when('/options/groups', '/options/groups/tavern') .when('/options/groups/guilds', '/options/groups/guilds/public') .when('/options/groups/hall', '/options/groups/hall/heroes') - .when('/options/groups', '/options/groups/tavern') .when('/options/inventory', '/options/inventory/drops') .when('/options/settings', '/options/settings/settings') From 63c2248b38b9449e5f15adfce6ac06efdfac6597 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 14 Dec 2014 20:07:46 -0600 Subject: [PATCH 47/56] Corrected guild links --- views/options/social/index.jade | 2 +- views/shared/header/menu.jade | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/views/options/social/index.jade b/views/options/social/index.jade index 89a1e98e7e..32100f2aff 100644 --- a/views/options/social/index.jade +++ b/views/options/social/index.jade @@ -105,7 +105,7 @@ script(type='text/ng-template', id='partials/options.social.html') a(ui-sref='options.social.party') =env.t('party') li(ng-class="{ active: $state.includes('options.social.guilds') }") - a(ui-sref='options.social.guilds') + a(ui-sref='options.social.guilds.public') =env.t('guilds') li(ng-class="{ active: $state.includes('options.social.challenges') }") a(ui-sref='options.social.challenges') diff --git a/views/shared/header/menu.jade b/views/shared/header/menu.jade index c52c700c27..5ba7fe1dfb 100644 --- a/views/shared/header/menu.jade +++ b/views/shared/header/menu.jade @@ -110,7 +110,7 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}') li a(ui-sref='options.social.party')=env.t('party') li - a(ui-sref='options.social.guilds')=env.t('guilds') + a(ui-sref='options.social.guilds.public')=env.t('guilds') li a(ui-sref='options.social.challenges')=env.t('challenges') li From d56eb1eeeb5600e76b3f839b61d77810626148a2 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 14 Dec 2014 20:10:42 -0600 Subject: [PATCH 48/56] Added last guild link, corrected hall links --- views/options/social/index.jade | 2 +- views/shared/header/menu.jade | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/views/options/social/index.jade b/views/options/social/index.jade index 32100f2aff..24b86ab7e5 100644 --- a/views/options/social/index.jade +++ b/views/options/social/index.jade @@ -111,7 +111,7 @@ script(type='text/ng-template', id='partials/options.social.html') a(ui-sref='options.social.challenges') =env.t('challenges') li(ng-class="{ active: $state.includes('options.social.hall') }") - a(ui-sref='options.social.hall') + a(ui-sref='options.social.hall.heroes') =env.t('hall') .tab-content diff --git a/views/shared/header/menu.jade b/views/shared/header/menu.jade index 5ba7fe1dfb..024c9463d4 100644 --- a/views/shared/header/menu.jade +++ b/views/shared/header/menu.jade @@ -34,11 +34,11 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}') li a(ui-sref='options.social.party')=env.t('party') li - a(ui-sref='options.social.guilds')=env.t('guilds') + a(ui-sref='options.social.guilds.public')=env.t('guilds') li a(ui-sref='options.social.challenges')=env.t('challenges') li - a(ui-sref='options.social.hall')=env.t('hall') + a(ui-sref='options.social.hall.heroes')=env.t('hall') ul.toolbar-submenu li a(ui-sref='options.inventory.drops')=env.t('market') @@ -114,7 +114,7 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}') li a(ui-sref='options.social.challenges')=env.t('challenges') li - a(ui-sref='options.social.hall')=env.t('hall') + a(ui-sref='options.social.hall.heroes')=env.t('hall') li.toolbar-button-dropdown a(ui-sref='options.inventory.drops') span=env.t('inventory') From 43961a5ab948def947406673380c2ddb4df6b596 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Tue, 16 Dec 2014 11:43:58 -0700 Subject: [PATCH 49/56] refactor(iap): use env var for google IAP, intead of key file. See https://github.com/voltrue2/in-app-purchase/commit/cb31043281dc1221c61efb2dfbd13687761bceca cc @negue --- config.json.example | 2 +- package.json | 2 +- src/controllers/payments/iap.js | 6 ------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/config.json.example b/config.json.example index 74e2f0822c..196ceb369f 100644 --- a/config.json.example +++ b/config.json.example @@ -42,7 +42,7 @@ "client_id":"client_id", "client_secret":"client_secret" }, - "IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/", + "GOOGLE_IAB_PUBLICKEY_LIVE": "GOOGLE_IAB_PUBLICKEY_LIVE", "LOGGLY": { "enabled": false, "subdomain": "subdomain", diff --git a/package.json b/package.json index 6f14a60e51..84fd5bccfc 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "grunt-nodemon": "~0.3.0", "habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared#develop", "icalendar": "git://github.com/lefnire/node-icalendar#master", - "in-app-purchase": "^0.2.0", + "in-app-purchase": "^0.3.0", "jade": "~1.7.0", "js2xmlparser": "~0.1.2", "lodash": "~2.4.1", diff --git a/src/controllers/payments/iap.js b/src/controllers/payments/iap.js index 29b03fedfb..9dcef12efc 100644 --- a/src/controllers/payments/iap.js +++ b/src/controllers/payments/iap.js @@ -3,12 +3,6 @@ var async = require('async'); var payments = require('./index'); var nconf = require('nconf'); -var inAppPurchase = require('in-app-purchase'); -inAppPurchase.config({ - // this is the path to the directory containing iap-sanbox/iap-live files - googlePublicKeyPath: nconf.get("IAP_GOOGLE_KEYDIR") -}); - // Validation ERROR Codes var INVALID_PAYLOAD = 6778001; var CONNECTION_FAILED = 6778002; From 709fb2b706cb49a1393a3f61a60a858dede1a983 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Tue, 16 Dec 2014 13:05:39 -0700 Subject: [PATCH 50/56] Revert "refactor(iap): use env var for google IAP". orders are being left in "Chargeable - Delivered" for some reason. This reverts commit 43961a5ab948def947406673380c2ddb4df6b596. --- .gitignore | 1 - config.json.example | 2 +- keys/google/iap-live | 1 + package.json | 2 +- src/controllers/payments/iap.js | 6 ++++++ 5 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 keys/google/iap-live diff --git a/.gitignore b/.gitignore index 5b5e6f87bd..1717063857 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,3 @@ test/*.js test/*.map public/docs *.sublime-workspace -keys diff --git a/config.json.example b/config.json.example index 196ceb369f..74e2f0822c 100644 --- a/config.json.example +++ b/config.json.example @@ -42,7 +42,7 @@ "client_id":"client_id", "client_secret":"client_secret" }, - "GOOGLE_IAB_PUBLICKEY_LIVE": "GOOGLE_IAB_PUBLICKEY_LIVE", + "IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/", "LOGGLY": { "enabled": false, "subdomain": "subdomain", diff --git a/keys/google/iap-live b/keys/google/iap-live new file mode 100644 index 0000000000..69dc38136b --- /dev/null +++ b/keys/google/iap-live @@ -0,0 +1 @@ +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk6EVccD7D6y5vLmsUxUXVYTMyByq+NjfTzjMBaKCk93fq8Ahn6Ipha1K4oBIvArUj3CsP5Tb/Yf6c6SrEsSnE3eqgKQgeq+fYyfIm+Fb+tFGTttLonZ2Sqemqeyx2PjOG2CVZBXj1+Zr7jb0ocMNYraYUTvklWnpcK6/5ZaZLLent1PGiU9l5jIfRAsxmJH/UpssgpFPnCs+8PRKUALkJATTfVCCF+PJef6mcKAafAArKUEo0lR2/NeaE62Wpii33DLuKDPfMqL3g4EHZTEVE1ezFl/3WPeLlxuQm38hZLpEfEYKp17qzplU+GmWIfjZt6OdtVdonlYxIxNAZLnWEQIDAQAB \ No newline at end of file diff --git a/package.json b/package.json index 84fd5bccfc..6f14a60e51 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "grunt-nodemon": "~0.3.0", "habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared#develop", "icalendar": "git://github.com/lefnire/node-icalendar#master", - "in-app-purchase": "^0.3.0", + "in-app-purchase": "^0.2.0", "jade": "~1.7.0", "js2xmlparser": "~0.1.2", "lodash": "~2.4.1", diff --git a/src/controllers/payments/iap.js b/src/controllers/payments/iap.js index 9dcef12efc..29b03fedfb 100644 --- a/src/controllers/payments/iap.js +++ b/src/controllers/payments/iap.js @@ -3,6 +3,12 @@ var async = require('async'); var payments = require('./index'); var nconf = require('nconf'); +var inAppPurchase = require('in-app-purchase'); +inAppPurchase.config({ + // this is the path to the directory containing iap-sanbox/iap-live files + googlePublicKeyPath: nconf.get("IAP_GOOGLE_KEYDIR") +}); + // Validation ERROR Codes var INVALID_PAYLOAD = 6778001; var CONNECTION_FAILED = 6778002; From 2822ce233fae8fbf422c064d46c1292b21e17220 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Tue, 16 Dec 2014 14:17:38 -0700 Subject: [PATCH 51/56] fix(inn): see https://github.com/HabitRPG/habitrpg-shared/pull/231 --- views/options/social/tavern.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/options/social/tavern.jade b/views/options/social/tavern.jade index 9eea1159f3..0739126588 100644 --- a/views/options/social/tavern.jade +++ b/views/options/social/tavern.jade @@ -21,7 +21,7 @@ span(ng-hide='user.preferences.sleep')=env.t('innCheckIn') =env.t('danielText2') .alert.alert-info(ng-show='user.preferences.sleep') - =env.t('innText') + =env.t('innText',{name:"{{user.profile.name}}"}) // Resources .panel.panel-default From d91d9e1eb3d04b180701d40542e7ae4fc554a919 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Tue, 16 Dec 2014 16:30:51 -0700 Subject: [PATCH 52/56] refactor(facebook): use async.auto to collect multiple vars, cleaner than waterfall --- src/controllers/auth.js | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/controllers/auth.js b/src/controllers/auth.js index 4e3877ca3f..67ddbc08f5 100644 --- a/src/controllers/auth.js +++ b/src/controllers/auth.js @@ -148,21 +148,19 @@ api.loginSocial = function(req, res, next) { network = req.body.network; if (network!=='facebook') return res.json(401, {err:"Only Facebook supported currently."}); - async.waterfall([ - function(cb){ + async.auto({ + profile: function (cb) { passport._strategies[network].userProfile(access_token, cb); }, - function(profile, cb) { - var q = {};q['auth.'+network+'.id'] = profile.id; - User.findOne(q, {_id:1, apiToken:1, auth:1}, function(err, user){ - if (err) return cb(err); - cb(null, {user:user, profile:profile}); - }); - }, - function(data, cb){ - if (data.user) return cb(null, data.user); + user: ['profile', function (cb, results) { + var q = {}; + q['auth.' + network + '.id'] = results.profile.id; + User.findOne(q, {_id: 1, apiToken: 1, auth: 1}, cb); + }], + register: ['profile', 'user', function (cb, results) { + if (results.user) return cb(null, results.user); // Create new user - var prof = data.profile; + var prof = results.profile; var user = { preferences: { language: req.language // User language detected from browser, not saved @@ -175,15 +173,16 @@ api.loginSocial = function(req, res, next) { user = new User(user); user.save(cb); - if(isProd && prof.emails && prof.emails[0] && prof.emails[0].value){ - utils.txnEmail({name:prof.displayName || prof.username, email:prof.emails[0].value}, 'welcome'); + if (isProd && prof.emails && prof.emails[0] && prof.emails[0].value) { + utils.txnEmail({name: prof.displayName || prof.username, email: prof.emails[0].value}, 'welcome'); } ga.event('register', network).send(); - } - ], function(err, user){ + }] + }, function(err, results){ if (err) return res.json(401, {err: err.toString ? err.toString() : err}); - if (user.auth.blocked) return res.json(401, accountSuspended(user._id)); - return res.json(200, {id: user.id, token:user.apiToken}); + var acct = results.register[0] ? results.register[0] : results.register; + if (acct.auth.blocked) return res.json(401, accountSuspended(acct._id)); + return res.json(200, {id:acct._id, token:acct.apiToken}); }) }; From 58957118c0d38d23e139b53d40e1d195ea6d0826 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 16 Dec 2014 20:18:55 -0600 Subject: [PATCH 53/56] chore(news): Bailey for Seasonal Shop --- views/shared/new-stuff.jade | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/views/shared/new-stuff.jade b/views/shared/new-stuff.jade index b059de69df..632780c771 100644 --- a/views/shared/new-stuff.jade +++ b/views/shared/new-stuff.jade @@ -1,19 +1,41 @@ -h5 PENGUIN PET QUEST AND WINTER PLOT-LINE +h5 ANDROID APP UPDATE, SEASONAL SHOP, AND WINTER PLOT-LINE CONTINUES tr td - h5 Penguin Pet Quest - .quest_penguin.pull-right - p Habiticans wanted to go ice-skating, but instead, a giant penguin is freezing everything in sight! All we wanted was to go ice-skating... Can you get this penguin to chill out? If so, you'll be rewarded with some penguins of your own! - p.small.muted by Melynnrose, Breadstrings, Rattify, Painter de Cluster, Daniel the Bard, and Leephon + h5 Android App Update: December Art and Buying Gems! + .background_south_pole.pull-right + p The December backgrounds and penguin pet quest are now visible in the Android mobile app! Also, we’ve made it possible to buy gems directly from the app. Now you don’t have to switch to the website to stock up! + p You can get the Android app here! We will announce when the iOS app is available as well. + p.small.muted by negue tr td - h5 Winter Plot-Line - p Lemoness enters the Tavern with worrying news from the far north of Habitica. "Nobody's heard from the Stoïkalm Steppes for over a week," she says. "It's hard to imagine anything troubling the citizens there, since it's such a placid part of the continent... But just in case, maybe I should pay a visit." Sounds like a good plan to us! + h5 Seasonal Shop Tab + .seasonalshop_closed.pull-right + p Looks like a new tab has appeared under Inventory - the Seasonal Shop! It's still closed, but I've heard a rumor that it will open soon... + p.small.muted by SabreCat and Lemoness + tr + td + h5 Winter Plot-Line Continues + p Lemoness bursts into the Tavern, shaking icicles off her hat. "The Stoïkalm Steppes are completely abandoned!" she says, gulping the cup of tea that Daniel the Barkeep offers her. "No people milling about, no mounts and pets playing in the snow - and when I tried to fly closer, my dragon spooked and refused to land!" + p A cloaked figure in the corner steps into the fire light - SabreCat, a powerful adventurer from the north. "The Stoïkalm Steppes are the last home of many animals that have long since gone extinct elsewhere," he says. "The stoic Stoïkalmers would never flee their lands unless something was threatening their pets and mounts!" + p He turns to Lemoness. "I can speak the language of the northern beasts. I'll try to contact the roaming sabertooth prides to see if they know what happened." As he lopes off into the distance, a cold wind begins to blow. + p.muted Missed the first part of the Winter Plot-Line? Read it here. hr a(href='/static/old-news', target='_blank') Read older news mixin oldNews + h5 12/9/2014 - Penguin Pet Quest and Winter Plot-Line + tr + td + h5 Penguin Pet Quest + .quest_penguin.pull-right + p Habiticans wanted to go ice-skating, but instead, a giant penguin is freezing everything in sight! All we wanted was to go ice-skating... Can you get this penguin to chill out? If so, you'll be rewarded with some penguins of your own! + p.small.muted by Melynnrose, Breadstrings, Rattify, Painter de Cluster, Daniel the Bard, and Leephon + tr + td + h5 Winter Plot-Line + p Lemoness enters the Tavern with worrying news from the far north of Habitica. "Nobody's heard from the Stoïkalm Steppes for over a week," she says. "It's hard to imagine anything troubling the citizens there, since it's such a placid part of the continent... But just in case, maybe I should pay a visit." Sounds like a good plan to us! + h5 12/3/2014 - Gifting Subscriptions And Gems, New Subscription Benefits, Mysterious Time Travelers, Steampunk Item Sets, And Block Subscriptions! table.table.table-striped tr From dbe608bc3aa06606cea20867bcdb63d40c516bc1 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 16 Dec 2014 20:24:04 -0600 Subject: [PATCH 54/56] fix(news): correct link and awkward image --- views/shared/new-stuff.jade | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/views/shared/new-stuff.jade b/views/shared/new-stuff.jade index 632780c771..8162251f90 100644 --- a/views/shared/new-stuff.jade +++ b/views/shared/new-stuff.jade @@ -2,7 +2,6 @@ h5 ANDROID APP UPDATE, SEASONAL SHOP, AND WINTER PLOT-LINE CONTINUES tr td h5 Android App Update: December Art and Buying Gems! - .background_south_pole.pull-right p The December backgrounds and penguin pet quest are now visible in the Android mobile app! Also, we’ve made it possible to buy gems directly from the app. Now you don’t have to switch to the website to stock up! p You can get the Android app here! We will announce when the iOS app is available as well. p.small.muted by negue @@ -10,7 +9,7 @@ h5 ANDROID APP UPDATE, SEASONAL SHOP, AND WINTER PLOT-LINE CONTINUES td h5 Seasonal Shop Tab .seasonalshop_closed.pull-right - p Looks like a new tab has appeared under Inventory - the Seasonal Shop! It's still closed, but I've heard a rumor that it will open soon... + p Looks like a new tab has appeared under Inventory - the Seasonal Shop! It's still closed, but I've heard a rumor that it will open soon... p.small.muted by SabreCat and Lemoness tr td From 8ff0a4a4a435dd9c01af09d7accbe7eff19436a9 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 17 Dec 2014 18:27:37 -0600 Subject: [PATCH 55/56] fix(seasonal): Leslie link --- views/options/inventory/inventory.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/options/inventory/inventory.jade b/views/options/inventory/inventory.jade index 929919d94b..da48ed04fc 100644 --- a/views/options/inventory/inventory.jade +++ b/views/options/inventory/inventory.jade @@ -27,7 +27,7 @@ script(type='text/ng-template', id='partials/options.inventory.seasonalshop.html .col-md-10 .popover.static-popover.fade.right.in .arrow - h3.popover-title!=env.t('seasonalShopClosedTitle') + h3.popover-title!=env.t('seasonalShopClosedTitle', {linkStart:"", linkEnd: ""}) .popover-content p!=env.t('seasonalShopClosedText', {linkStart: "", linkEnd: ""}) From dc18569e78201dd5ee736483721a78ca3a9eb0e9 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 17 Dec 2014 22:07:49 -0700 Subject: [PATCH 56/56] fix(phantom): downgrade to node 0.10.22 due to CERT error, see https://github.com/Medium/phantomjs/issues/262 (cc @sabrecat) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6f14a60e51..b6b97ba199 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "www.habitrpg.com" ], "engines": { - "node": "0.10.x", + "node": "0.10.22", "npm": "1.2.x" }, "scripts": {