From 2d44783d807256a0a04e5e56d0fa1eafb4620fe5 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 5 Feb 2015 15:45:45 -0700 Subject: [PATCH 01/12] fix(tour): don't restart tour on userUpdated, fixes #4632 --- public/js/services/guideServices.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/js/services/guideServices.js b/public/js/services/guideServices.js index 74d710dbe5..45f02161e0 100644 --- a/public/js/services/guideServices.js +++ b/public/js/services/guideServices.js @@ -12,9 +12,11 @@ function($rootScope, User, $timeout, $state) { * this because we need to determine whether to show the tour *after* the user has been pulled from the server, * otherwise it's always start off as true, and then get set to false later */ + var tourRunning = false; $rootScope.$on('userUpdated', initTour); function initTour(){ - if (User.user.flags.showTour === false) return; + if (User.user.flags.showTour === false || tourRunning) return; + tourRunning = true; var tourSteps = [ { orphan:true, From ef8b783f2bd9d7b711caee5c054b291afb086c90 Mon Sep 17 00:00:00 2001 From: Alice Harris Date: Sat, 7 Feb 2015 13:49:38 +1000 Subject: [PATCH 02/12] add script that helps a database admin find and remove tasks with duplicated IDs for a single user --- migrations/duplicatedTasksFindAndRemove.js | 89 ++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 migrations/duplicatedTasksFindAndRemove.js diff --git a/migrations/duplicatedTasksFindAndRemove.js b/migrations/duplicatedTasksFindAndRemove.js new file mode 100644 index 0000000000..79900dd71e --- /dev/null +++ b/migrations/duplicatedTasksFindAndRemove.js @@ -0,0 +1,89 @@ +/* + * IMPORTANT: + * + * DO NOT TRUST THIS SCRIPT YET + * + * + * This has been written by Alys to identify and remove duplicated tasks + * i.e., tasks that have the same `id` value as another task. + * However it could almost certainly be improved (the aggregation step HAS + * to be easier that this!) and Alys is still working on it. Improvements + * welcome. + * + * If you use it, do ALL of the following things: + * + * - configuration, as described below + * - make a full backup of the user's data + * - be aware of how to restore the user's data from that backup + * - test the script first on a local copy of the database + * - dump the user's data to a text file before running the script so that + * it can later be compared to a dump made afterwards + * - run the script once first with both of the db.users.update() commands + * commented-out and check that the printed task IDs are correct + * - run the script with all code enabled + * - dump the user's data to a text file after running the script + * - diff the two dumps to ensure that only the correct tasks were removed + * + * + * When two tasks exist with the same ID, only one of those tasks will be + * removed (whichever copy the script finds first). + * If three tasks exist with the same ID, you'll probably need to run this + * script twice. + * + */ + + +// CONFIGURATION: +// - Change the uuid below to be the user's uuid. +// - Change ALL instances of "todos" to "habits"/"dailys"/"rewards" as +// needed. Do not miss any of them! + + +var uuid='30fb2640-7121-4968-ace5-f385e60ea6c5'; + +db.users.aggregate([ + {$match: {'_id': uuid} }, + {$project: { + '_id':0, 'todos':1 + }}, + {$unwind: '$todos'}, + {$group: { + _id: { taskid: '$todos.id' }, + count: { $sum: 1 } + }}, + { $match: { + count: { $gt: 1 } + }}, + { $project: { + '_id.taskid':1, + }}, + {$group: { + _id: { taskid: '$todos.id' }, + troublesomeIds: { $addToSet: "$_id.taskid" }, + }}, + { $project: { + '_id':0, + troublesomeIds:1, + }}, +]).forEach( + function(data) { + // print( "\n" ); printjson(data); + data.troublesomeIds.forEach( function(taskid) { + print('non-unique task: ' + taskid); + db.users.update({ + '_id': uuid, + 'todos': { $elemMatch: { id: taskid } } + }, + { $set: { "todos.$.id" : 'de666' } + }); + }); + } +); + +db.users.update( + {'_id': uuid}, + { $pull: { todos: { id: 'de666' } } }, + { multi: false } +); + + From 734227c47281a95359565ad7410596f588916729 Mon Sep 17 00:00:00 2001 From: Alice Harris Date: Sat, 7 Feb 2015 14:00:21 +1000 Subject: [PATCH 03/12] improve spacing --- migrations/duplicatedTasksFindAndRemove.js | 91 +++++++++++----------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/migrations/duplicatedTasksFindAndRemove.js b/migrations/duplicatedTasksFindAndRemove.js index 79900dd71e..a6288efc4a 100644 --- a/migrations/duplicatedTasksFindAndRemove.js +++ b/migrations/duplicatedTasksFindAndRemove.js @@ -4,14 +4,14 @@ * DO NOT TRUST THIS SCRIPT YET * * - * This has been written by Alys to identify and remove duplicated tasks + * This has been written by Alys to identify and remove duplicated tasks * i.e., tasks that have the same `id` value as another task. * However it could almost certainly be improved (the aggregation step HAS * to be easier that this!) and Alys is still working on it. Improvements * welcome. * * If you use it, do ALL of the following things: - * + * * - configuration, as described below * - make a full backup of the user's data * - be aware of how to restore the user's data from that backup @@ -21,15 +21,15 @@ * - run the script once first with both of the db.users.update() commands * commented-out and check that the printed task IDs are correct * - run the script with all code enabled - * - dump the user's data to a text file after running the script + * - dump the user's data to a text file after running the script * - diff the two dumps to ensure that only the correct tasks were removed - * - * + * + * * When two tasks exist with the same ID, only one of those tasks will be * removed (whichever copy the script finds first). * If three tasks exist with the same ID, you'll probably need to run this * script twice. - * + * */ @@ -42,48 +42,49 @@ var uuid='30fb2640-7121-4968-ace5-f385e60ea6c5'; db.users.aggregate([ - {$match: {'_id': uuid} }, - {$project: { - '_id':0, 'todos':1 - }}, - {$unwind: '$todos'}, - {$group: { - _id: { taskid: '$todos.id' }, - count: { $sum: 1 } - }}, - { $match: { - count: { $gt: 1 } - }}, - { $project: { - '_id.taskid':1, - }}, - {$group: { - _id: { taskid: '$todos.id' }, - troublesomeIds: { $addToSet: "$_id.taskid" }, - }}, - { $project: { - '_id':0, - troublesomeIds:1, - }}, + {$match: { + '_id': uuid + }}, + {$project: { + '_id':0, 'todos':1 + }}, + {$unwind: '$todos'}, + {$group: { + _id: { taskid: '$todos.id' }, + count: { $sum: 1 } + }}, + {$match: { + count: { $gt: 1 } + }}, + {$project: { + '_id.taskid':1, + }}, + {$group: { + _id: { taskid: '$todos.id' }, + troublesomeIds: { $addToSet: "$_id.taskid" }, + }}, + {$project: { + '_id':0, + troublesomeIds:1, + }}, ]).forEach( - function(data) { - // print( "\n" ); printjson(data); - data.troublesomeIds.forEach( function(taskid) { - print('non-unique task: ' + taskid); - db.users.update({ - '_id': uuid, - 'todos': { $elemMatch: { id: taskid } } - }, - { $set: { "todos.$.id" : 'de666' } - }); - }); - } + function(data) { + // print( "\n" ); printjson(data); + data.troublesomeIds.forEach( function(taskid) { + print('non-unique task: ' + taskid); + db.users.update({ + '_id': uuid, + 'todos': { $elemMatch: { id: taskid } } + },{ + $set: { "todos.$.id" : 'de666' } + }); + }); + } ); db.users.update( - {'_id': uuid}, - { $pull: { todos: { id: 'de666' } } }, - { multi: false } + {'_id': uuid}, + {$pull: { todos: { id: 'de666' } } }, + {multi: false } ); - From 4f66d3044132f6b12bc431a13a75768e376ac83d Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Sat, 7 Feb 2015 21:36:38 +0100 Subject: [PATCH 04/12] implement quest started email --- src/controllers/groups.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/controllers/groups.js b/src/controllers/groups.js index 1754175228..e7d1f89a47 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -30,10 +30,12 @@ var guildPopulate = {path: 'members', select: nameFields, options: {limit: 15} } * limited fields - and only a sampling of the members, beacuse they can be in the thousands * @param type: 'party' or otherwise * @param q: the Mongoose query we're building up + * @param additionalFields: if we want to populate some additional field not fetched normally + * pass it as a string, parties only */ -var populateQuery = function(type, q){ +var populateQuery = function(type, q, additionalFields){ if (type == 'party') - q.populate('members', partyFields); + q.populate('members', partyFields + (additionalFields ? (' ' + additionalFields) : '')); else q.populate(guildPopulate); q.populate('invites', nameFields); @@ -673,6 +675,7 @@ questStart = function(req, res, next) { if (m == group.quest.leader) updates['$inc']['items.quests.'+key] = -1; if (group.quest.members[m] == true) { + console.log(m); // See https://github.com/HabitRPG/habitrpg/issues/2168#issuecomment-31556322 , we need to *not* reset party.quest.progress.up //updates['$set']['party.quest'] = Group.cleanQuestProgress({key:key,progress:{collect:collected}}); updates['$set']['party.quest.key'] = key; @@ -700,7 +703,8 @@ questStart = function(req, res, next) { parallel.push(function(cb2){group.save(cb2)}); parallel.push(function(cb){ - populateQuery(group.type, Group.findById(group._id)).exec(cb); + // Fetch user.auth to send email, then remove it from data sent to the client + populateQuery(group.type, Group.findById(group._id), 'auth.facebook auth.local').exec(cb); }); async.parallel(parallel,function(err, results){ @@ -711,7 +715,25 @@ questStart = function(req, res, next) { groupClone.members = results[lastIndex].members; + // Send quest started email and remove auth information + _.each(groupClone.members, function(user){ + + if(user.preferences.emailNotifications.questStarted !== false && + user._id !== res.locals.user._id && + group.quest.members[user._id] == true + ){ + utils.txnEmail(user, 'quest-started', [ + {name: 'PARTY_URL', content: nconf.get('BASE_URL') + '/#/options/groups/party'} + ]); + } + + // Remove sensitive data from what is sent to the public + user.auth.facebook = undefined; + user.auth.local = undefined; + }); + group = null; + return res.json(groupClone); }); } From 7d139b4adc9e0bbf0fe5b643f64056e8e1f37180 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Sat, 7 Feb 2015 22:20:16 +0100 Subject: [PATCH 05/12] add quest invitation email (disabled for now) and fixes --- src/controllers/groups.js | 24 ++++++++++++++++++++++-- src/models/user.js | 2 ++ views/options/settings.jade | 10 ++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/controllers/groups.js b/src/controllers/groups.js index e7d1f89a47..ba6b007a17 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -675,7 +675,6 @@ questStart = function(req, res, next) { if (m == group.quest.leader) updates['$inc']['items.quests.'+key] = -1; if (group.quest.members[m] == true) { - console.log(m); // See https://github.com/HabitRPG/habitrpg/issues/2168#issuecomment-31556322 , we need to *not* reset party.quest.progress.up //updates['$set']['party.quest'] = Group.cleanQuestProgress({key:key,progress:{collect:collected}}); updates['$set']['party.quest.key'] = key; @@ -765,12 +764,33 @@ api.questAccept = function(req, res, next) { } }); + User.find({ + _id: { + $in: _.without(group.members, user._id) + } + }, {auth: 1, preferences: 1, profile: 1}, function(err, members){ + if(err) return next(err); + + _.each(members, function(member){ + if(member.preferences.emailNotifications.invitedQuest !== false && false + ){ + utils.txnEmail(member, ('invite-' + (quest.boss ? 'boss' : 'collection') + '-quest'), [ + {name: 'QUEST_NAME', content: quest.text()}, + {name: 'INVITER', content: utils.getUserInfo(user, ['name']).name}, + {name: 'PARTY_URL', content: nconf.get('BASE_URL') + '/#/options/groups/party'} + ]); + } + }); + + questStart(req,res,next); + }); + // Party member accepting the invitation } else { if (!group.quest.key) return res.json(400,{err:'No quest invitation has been sent out yet.'}); group.quest.members[user._id] = true; + questStart(req,res,next); } - questStart(req,res,next); } api.questReject = function(req, res, next) { diff --git a/src/models/user.js b/src/models/user.js index 62862bfd75..74444d4ff5 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -308,6 +308,8 @@ var UserSchema = new Schema({ giftedSubscription: {type: Boolean, 'default': true}, invitedParty: {type: Boolean, 'default': true}, invitedGuild: {type: Boolean, 'default': true}, + questStarted: {type: Boolean, 'default': true}, + invitedQuest: {type: Boolean, 'default': true}, //remindersToLogin: {type: Boolean, 'default': true}, importantAnnouncements: {type: Boolean, 'default': true} } diff --git a/views/options/settings.jade b/views/options/settings.jade index a5cee15b45..d1b961fdf9 100644 --- a/views/options/settings.jade +++ b/views/options/settings.jade @@ -305,6 +305,16 @@ script(id='partials/options.settings.notifications.html', type="text/ng-template input(type='checkbox', ng-disabled='user.preferences.emailNotifications.unsubscribeFromAll === true', ng-model='user.preferences.emailNotifications.invitedGuild', ng-change='set({"preferences.emailNotifications.invitedGuild": user.preferences.emailNotifications.invitedGuild ? true: false})') span=env.t('invitedGuild') + .checkbox + label + input(type='checkbox', ng-disabled='user.preferences.emailNotifications.unsubscribeFromAll === true', ng-model='user.preferences.emailNotifications.questStarted', ng-change='set({"preferences.emailNotifications.questStarted": user.preferences.emailNotifications.questStarted ? true: false})') + span=env.t('questStarted') + + .checkbox + label + input(type='checkbox', ng-disabled='user.preferences.emailNotifications.unsubscribeFromAll === true', ng-model='user.preferences.emailNotifications.invitedQuest', ng-change='set({"preferences.emailNotifications.invitedQuest": user.preferences.emailNotifications.invitedQuest ? true: false})') + span=env.t('invitedQuest') + //.checkbox label input(type='checkbox', ng-disabled='user.preferences.emailNotifications.unsubscribeFromAll === true', ng-model='user.preferences.emailNotifications.remindersToLogin', ng-change='set({"preferences.emailNotifications.remindersToLogin": user.preferences.emailNotifications.remindersToLogin ? true: false})') From 58cec5259a32cda7fccb90ae9c82056450bffb7f Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Sun, 8 Feb 2015 20:33:03 +0100 Subject: [PATCH 06/12] enable quest invitation email --- src/controllers/groups.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/controllers/groups.js b/src/controllers/groups.js index ba6b007a17..7d874e0d50 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -771,12 +771,13 @@ api.questAccept = function(req, res, next) { }, {auth: 1, preferences: 1, profile: 1}, function(err, members){ if(err) return next(err); + var inviterName = utils.getUserInfo(user, ['name']).name; + _.each(members, function(member){ - if(member.preferences.emailNotifications.invitedQuest !== false && false - ){ + if(member.preferences.emailNotifications.invitedQuest !== false){ utils.txnEmail(member, ('invite-' + (quest.boss ? 'boss' : 'collection') + '-quest'), [ {name: 'QUEST_NAME', content: quest.text()}, - {name: 'INVITER', content: utils.getUserInfo(user, ['name']).name}, + {name: 'INVITER', content: inviterName}, {name: 'PARTY_URL', content: nconf.get('BASE_URL') + '/#/options/groups/party'} ]); } From 8b88b344752e09cf6e2b82133e44d925c9cf470c Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Sun, 8 Feb 2015 21:45:30 +0100 Subject: [PATCH 07/12] correctly redirect after authentication --- public/js/controllers/authCtrl.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/js/controllers/authCtrl.js b/public/js/controllers/authCtrl.js index a167ecb309..6bdd4c0b83 100644 --- a/public/js/controllers/authCtrl.js +++ b/public/js/controllers/authCtrl.js @@ -15,7 +15,7 @@ angular.module('habitrpg') var runAuth = function(id, token) { User.authenticate(id, token, function(err) { - $window.location.href = '/'; + $window.location.href = ('/' + window.location.hash); }); }; @@ -57,7 +57,7 @@ angular.module('habitrpg') $scope.playButtonClick = function(){ window.ga && ga('send', 'event', 'button', 'click', 'Play'); if (User.authenticated()) { - window.location.href = '/#/tasks'; + window.location.href = ('/' + window.location.hash); } else { $modal.open({ templateUrl: 'modals/login.html' From 4b96ee83b609bd438e1161bae151756c090a868e Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sun, 8 Feb 2015 15:21:43 -0600 Subject: [PATCH 08/12] chore(news): 2/8 Bailey --- views/shared/new-stuff.jade | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/views/shared/new-stuff.jade b/views/shared/new-stuff.jade index 753b36a65c..a88b871f6e 100644 --- a/views/shared/new-stuff.jade +++ b/views/shared/new-stuff.jade @@ -1,16 +1,28 @@ -h5 2/3/2015 +h5 2/8/2015 - EMAIL NOTIFICATIONS AND LOGIN TYPE SWITCHING! hr tr td - h5 FEBRUARY BACKGROUNDS REVEALED - .background_distant_castle.pull-right - p There are three new avatar backgrounds in the Background Shop! Now your avatar can survey a Distant Castle, toil in the Blacksmithy, or explore a Crystal Cave! - p.small.muted by Holseties, Hanztan, and Twitching + h5 Email Notifications + p We've implemented email notifications for a variety of events, including receiving a Private Message, being invited to a Party, Guild, or Quest, and receiving a gift of Gems or a Subscription! We've got some more coming up, too, including the much-requested check-in reminders. + p Don't want to receive a certain type of notification? No problem! Just go to Notification Settings to tell us exactly which ones you do and do not want to receive. Our messenger dragons will be happy to comply! + p.small.muted by paglias and Lemoness + tr + td + h5 Login Type Switching + p Want to change your email address, or switch from Facebook login to email login (or vice versa)? Good news! Now you can switch it yourself, under Settings! + p.small.muted by Lefnire hr a(href='/static/old-news', target='_blank') Read older news mixin oldNews + h5 2/3/2015 + tr + td + h5 February Backgrounds Revealed + .background_distant_castle.pull-right + p There are three new avatar backgrounds in the Background Shop! Now your avatar can survey a Distant Castle, toil in the Blacksmithy, or explore a Crystal Cave! + p.small.muted by Holseties, Hanztan, and Twitching h5 2/2/2015 tr td From 9dfe18bc705d89eeae236ea0d65c441feb9d51e5 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Mon, 9 Feb 2015 17:43:14 +0100 Subject: [PATCH 09/12] typo --- src/controllers/payments/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/payments/index.js b/src/controllers/payments/index.js index af39684017..3ca68ffed6 100644 --- a/src/controllers/payments/index.js +++ b/src/controllers/payments/index.js @@ -76,7 +76,7 @@ exports.createSubscription = function(data, cb) { if (data.gift){ members.sendMessage(data.user, data.gift.member, data.gift); if(data.gift.member.preferences.emailNotifications.giftedSubscription !== false){ - utils.txnEmail(member, 'gifted-subscription', [ + utils.txnEmail(data.gift.member, 'gifted-subscription', [ {name: 'GIFTER', content: utils.getUserInfo(data.user, ['name']).name}, {name: 'X_MONTHS_SUBSCRIPTION', content: months} ]); From f303a71e66797858f77ae482fe56a5f6a597aeeb Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Mon, 9 Feb 2015 19:11:14 +0100 Subject: [PATCH 10/12] site can be used when sessionPartyInvite is invalid --- src/controllers/user.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index 2d7b1982f4..ae2987fb8c 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -459,7 +459,11 @@ api.sessionPartyInvite = function(req,res,next){ .select('invites members').exec(cb); }, function(group, cb){ - if (!group) return cb("Inviter not in party"); + if (!group){ + // Don't send error as it will prevent users from using the site + delete req.session.partyInvite; + return cb(); + } inv.party = req.session.partyInvite; delete req.session.partyInvite; if (!~group.invites.indexOf(res.locals.user._id)) From 8bd116f2c5b5d6b362cdc58fbfccd6cac2fe31c1 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Mon, 9 Feb 2015 19:31:18 +0100 Subject: [PATCH 11/12] fix gems gifting --- src/controllers/payments/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/payments/index.js b/src/controllers/payments/index.js index 3ca68ffed6..fe719bb66e 100644 --- a/src/controllers/payments/index.js +++ b/src/controllers/payments/index.js @@ -121,7 +121,7 @@ exports.buyGems = function(data, cb) { if (data.gift){ members.sendMessage(data.user, data.gift.member, data.gift); if(data.gift.member.preferences.emailNotifications.giftedGems !== false){ - utils.txnEmail(member, 'gifted-gems', [ + utils.txnEmail(data.gift.member, 'gifted-gems', [ {name: 'GIFTER', content: utils.getUserInfo(data.user, ['name']).name}, {name: 'X_GEMS_GIFTED', content: data.gift.gems.amount || 20} ]); From 24a6b2f4ccf155972443109154476dc5a19c5e03 Mon Sep 17 00:00:00 2001 From: Lisa Marie Date: Mon, 9 Feb 2015 19:39:05 -0700 Subject: [PATCH 12/12] migration(plans): sample code for plans which didn't get applied --- migrations/freeMonth.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/migrations/freeMonth.js b/migrations/freeMonth.js index 36f320746c..dba8d256ee 100644 --- a/migrations/freeMonth.js +++ b/migrations/freeMonth.js @@ -8,4 +8,25 @@ db.users.update( 'purchased.plan.planId':'basic_earned', 'purchased.plan.dateTerminated': moment().add('month',1).toDate() }} -) \ No newline at end of file +) + +// db.users.update( +// {_id:''}, +// {$set:{'purchased.plan':{ +// planId: 'basic_3mo', +// paymentMethod: 'Paypal', +// customerId: 'Gift', +// dateCreated: new Date(), +// dateTerminated: moment().add('month',3).toDate() +// dateUpdated: new Date(), +// extraMonths: 0, +// gemsBought: 0, +// mysteryItems: [], +// consecutive: { +// count: 0, +// offset: 3, +// gemCapExtra: 15, +// trinkets: 1 +// } +// }}} +// ) \ No newline at end of file