diff --git a/package.json b/package.json
index b6b97ba199..6f14a60e51 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,7 @@
"www.habitrpg.com"
],
"engines": {
- "node": "0.10.22",
+ "node": "0.10.x",
"npm": "1.2.x"
},
"scripts": {
diff --git a/src/controllers/auth.js b/src/controllers/auth.js
index 67ddbc08f5..3a00eda7f0 100644
--- a/src/controllers/auth.js
+++ b/src/controllers/auth.js
@@ -207,7 +207,6 @@ api.resetPassword = function(req, res, next){
user.auth.local.hashed_password = hashed_password;
utils.txnEmail(user, 'reset-password', [
{name: "NEW_PASSWORD", content: newPassword},
- {name: "URL", content: nconf.get('BASE_URL')},
{name: "USERNAME", content: user.auth.local.username}
]);
user.save(function(err){
diff --git a/src/controllers/user.js b/src/controllers/user.js
index 7bf164cd73..a7f80bf5f7 100644
--- a/src/controllers/user.js
+++ b/src/controllers/user.js
@@ -284,7 +284,13 @@ api.cron = function(req, res, next) {
User.findById(user._id, cb);
}
], function(err, saved) {
- if(err) logging.loggly({error: "Cron caught", stack: err.stack || err});
+ if(err) logging.loggly({
+ error: "Cron caught",
+ 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
+ });
res.locals.user = saved;
next(err,saved);
user = progress = quest = null;
diff --git a/src/models/coupon.js b/src/models/coupon.js
index c5122b6ba7..497b45697f 100644
--- a/src/models/coupon.js
+++ b/src/models/coupon.js
@@ -18,38 +18,34 @@ CouponSchema.statics.generate = function(event, count, callback) {
}
CouponSchema.statics.apply = function(user, code, next){
- var _coupon,_user;
- async.waterfall([
- function(cb) {
+ async.auto({
+ get_coupon: function (cb) {
mongoose.model('Coupon').findById(cc.validate(code), cb);
},
- function(coupon, cb) {
- _coupon = coupon;
- if (!coupon) return cb("Invalid coupon code");
- if (coupon.user) return cb("Coupon already used");
- switch (coupon.event) {
+ apply_coupon: ['get_coupon', function (cb, results) {
+ if (!results.get_coupon) return cb("Invalid coupon code");
+ if (results.get_coupon.user) return cb("Coupon already used");
+ switch (results.get_coupon.event) {
case 'wondercon':
- user.items.gear.owned.eyewear_special_wondercon_red = true;
- user.items.gear.owned.eyewear_special_wondercon_black = true;
- user.items.gear.owned.back_special_wondercon_black = true;
- user.items.gear.owned.back_special_wondercon_red = true;
- user.items.gear.owned.body_special_wondercon_red = true;
- user.items.gear.owned.body_special_wondercon_black = true;
- user.items.gear.owned.body_special_wondercon_gold = true;
+ user.items.gear.owned.eyewear_special_wondercon_red = true;
+ user.items.gear.owned.eyewear_special_wondercon_black = true;
+ user.items.gear.owned.back_special_wondercon_black = true;
+ user.items.gear.owned.back_special_wondercon_red = true;
+ user.items.gear.owned.body_special_wondercon_red = true;
+ user.items.gear.owned.body_special_wondercon_black = true;
+ user.items.gear.owned.body_special_wondercon_gold = true;
user.extra = {signupEvent: 'wondercon'};
user.save(cb);
break;
}
- },
- function(user, count, cb){
- _user = user;
- _coupon.user = user._id;
- _coupon.save(cb);
- }
- ], function(err){
+ }],
+ expire_coupon: ['apply_coupon', function (cb, results) {
+ results.get_coupon.user = user._id;
+ results.get_coupon.save(cb);
+ }]
+ }, function(err, results){
if (err) return next(err);
- next(null,_user);
- _coupon = _user = null;
+ next(null,results.apply_coupon[0]);
})
}
diff --git a/src/routes/apiv2.coffee b/src/routes/apiv2.coffee
index 0f8d272c69..6c93e3ba42 100644
--- a/src/routes/apiv2.coffee
+++ b/src/routes/apiv2.coffee
@@ -602,8 +602,12 @@ module.exports = (swagger, v2) ->
# ---------------------------------
# Members
# ---------------------------------
- "/members/{uuid}":
- spec:{}
+ "/members/{uuid}:GET":
+ spec:
+ path: '/members/{uuid}'
+ description: "Get a member."
+ parameters: [path('uuid','Member ID','string')]
+ middleware: [auth.auth, i18n.getUserLanguage]
action: members.getMember
"/members/{uuid}/message":
spec:
@@ -611,7 +615,7 @@ module.exports = (swagger, v2) ->
description: 'Send a private message to a member'
parameters: [
path 'uuid', 'The UUID of the member to message', 'string'
- body '', '{message: "The private message to send"}', 'object'
+ body '', '{"message": "The private message to send"}', 'object'
]
middleware: [auth.auth]
action: members.sendPrivateMessage
@@ -630,7 +634,7 @@ module.exports = (swagger, v2) ->
description: 'Send a gift to a member'
parameters: [
path 'uuid', 'The UUID of the member', 'string'
- body '', '{gems:{amount:Number, fromBalance:Boolean}, subscription:{months:Number}}', 'object'
+ body '', '{"type": "gems or subscription", "gems":{"amount":Number, "fromBalance":Boolean}, "subscription":{"months":Number}}', 'object'
]
middleware: [auth.auth]
action: members.sendGift
diff --git a/src/utils.js b/src/utils.js
index ba8dd47f95..f37d7cd9d0 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -35,6 +35,8 @@ function getMailingInfo(user) {
}
module.exports.txnEmail = function(mailingInfo, emailType, variables){
+ var variables = [{name: 'BASE_URL', content: nconf.get('BASE_URL')}].concat(variables || []);
+
if (mailingInfo._id) mailingInfo = getMailingInfo(mailingInfo);
if (!mailingInfo.email) return;
request({
diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee
index 28b2a97c3d..70521f91a1 100644
--- a/test/api.mocha.coffee
+++ b/test/api.mocha.coffee
@@ -141,7 +141,7 @@ describe "API", ->
id: res.body.todos[0].id
body:
- dateCompleted: moment().subtract("days", 4)
+ dateCompleted: moment().subtract(4, "days")
}
{
op: "updateTask"
@@ -149,7 +149,7 @@ describe "API", ->
id: res.body.todos[1].id
body:
- dateCompleted: moment().subtract("days", 4)
+ dateCompleted: moment().subtract(4, "days")
}
]).end (res) ->
expect(_.size(res.body.todos)).to.be 4
@@ -460,7 +460,7 @@ describe "API", ->
{
op: "update"
body:
- lastCron: moment().subtract("days", 1)
+ lastCron: moment().subtract(1, "days")
}
]).end (res) ->
expect(res.body.party.quest.progress.up).to.be.above up
@@ -532,7 +532,7 @@ describe "API", ->
{
op: "update"
body:
- lastCron: moment().subtract("days", 1)
+ lastCron: moment().subtract(1, "days")
}
]).end ->
cb()
@@ -608,7 +608,7 @@ describe "API", ->
it "Handles unsubscription", (done) ->
cron = ->
- user.lastCron = moment().subtract("d", 1)
+ user.lastCron = moment().subtract(1, "d")
user.fns.cron()
expect(user.purchased.plan.customerId).to.not.be.ok()
@@ -624,7 +624,7 @@ describe "API", ->
cron()
expect(user.purchased.plan.customerId).to.be.ok()
expect(user.purchased.plan.dateTerminated).to.be.ok()
- user.purchased.plan.dateTerminated = moment().subtract("d", 2)
+ user.purchased.plan.dateTerminated = moment().subtract(2, "d")
cron()
expect(user.purchased.plan.customerId).to.not.be.ok()
payments.createSubscription user,
diff --git a/views/options/inventory/inventory.jade b/views/options/inventory/inventory.jade
index da48ed04fc..d5d254c2a3 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', {linkStart:"", linkEnd: ""})
.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
diff --git a/views/options/profile.jade b/views/options/profile.jade
index cf8fef0e25..715b32072f 100644
--- a/views/options/profile.jade
+++ b/views/options/profile.jade
@@ -63,6 +63,7 @@ mixin customizeProfile(mobile)
button(type='button', ng-if='user.purchased.hair.color.#{color}', class='customize-option hair hair_bangs_1_#{color}', ng-click='unlock("hair.color.#{color}")')
+buyPref('hair.color', ['rainbow','yellow','green','purple','blue','TRUred'], 'rainbowColors')
+buyPref('hair.color', ['candycorn','ghostwhite','halloween','midnight','pumpkin','zombie'], 'hauntedColors', 'disabled')
+ +buyPref('hair.color', ['aurora','festive','hollygreen','peppermint','snowy','winterstar'], 'winteryColors')
li.customize-menu
menu(label=env.t('bodyHair'))
diff --git a/views/shared/new-stuff.jade b/views/shared/new-stuff.jade
index 8162251f90..76188b0e7b 100644
--- a/views/shared/new-stuff.jade
+++ b/views/shared/new-stuff.jade
@@ -1,28 +1,52 @@
-h5 ANDROID APP UPDATE, SEASONAL SHOP, AND WINTER PLOT-LINE CONTINUES
+h5 WINTER WONDERLAND BEGINS! Winter Class Outfits, Wintery Hair Colors, and NPC Decorations!
tr
td
- h5 Android App Update: December Art and Buying Gems!
- 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
+ h5 Winter Wonderland Begins!
+ p Winter has arrived, and the snow is gently drifting down over Habit City. Come celebrate with us!
tr
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.small.muted by SabreCat and Lemoness
+ h5 Winter Class Outfits
+ .promo_winterclasses2015.pull-right
+ p From now until January 31st, limited edition outfits are available in the Rewards column. Depending on your class, you can be a Soothing Skater, Mage of the North, Gingerbread Warrior, or Icicle Drake! You'd better get productive to earn enough gold before they disappear. Good luck!
+ p.small.muted by 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.
+ h5 Wintery Hair Colors
+ .promo_winteryhair.pull-right
+ p The Seasonal Edition Wintery Hair Colors are now available for purchase in the avatar customizations page! Now you can dye your avatar's hair Holly Green, Winter Star, Snowy, Peppermint, Aurora, or Festive.
+ p Seasonal Edition items recur unchanged every year, but they are only available to purchase during a short period of time. This is different from Limited Edition Items, which only recur if something is changed, such as the art or the price. These hair colors may remind some of you of the Holiday Hair Colors that were available last winter. The Holiday Hair Colors have been Retired in favor of the similar Seasonal Edition Wintery Hair Colors. Read more about the difference between Seasonal and Limited Edition items here!
+ p.small.muted by Lemoness, crystalphoenix, and mariahm
+ tr
+ td
+ h5 NPC Decorations
+ .npc_alex.pull-right
+ p Looks like the NPCs are really getting in to the cheery winter mood around the site. Who wouldn't? After all, there's plenty more to come!
+ p.small.muted by Lemoness
hr
a(href='/static/old-news', target='_blank') Read older news
mixin oldNews
+ h5 12/17/2014 - Android App Update, Seasonal Shop, and Winter Plot-Line Continues
+ tr
+ td
+ h5 Android App Update: December Art and Buying Gems!
+ 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 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.
h5 12/9/2014 - Penguin Pet Quest and Winter Plot-Line
tr
td