mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 07:07:35 +01:00
Merge branch 'develop' of github.com:HabitRPG/habitrpg into common-convert
Conflicts: src/controllers/auth.js src/controllers/challenges.js src/controllers/groups.js src/controllers/members.js src/controllers/payments/index.js src/controllers/user.js src/middleware.js src/models/user.js
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
var migrationName = '20150131_birthday_goodies_fix_remove_robe.js';
|
var migrationName = '20150131_birthday_goodies_fix__one_birthday__1';
|
||||||
var authorName = 'Alys'; // in case script author needs to know when their ...
|
var authorName = 'Alys'; // in case script author needs to know when their ...
|
||||||
var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* remove new birthday robes from people who don't have original birthday achievement
|
* remove new birthday robes and second achievement from people who shouldn't have them
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
|
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
|
||||||
@@ -13,12 +13,22 @@ var _ = require('lodash');
|
|||||||
|
|
||||||
var dbUsers = mongo.db(dbserver + '/habitrpg?auto_reconnect').collection('users');
|
var dbUsers = mongo.db(dbserver + '/habitrpg?auto_reconnect').collection('users');
|
||||||
|
|
||||||
|
// 'auth.timestamps.created':{$gt:new Date('2014-02-01')},
|
||||||
var query = {
|
var query = {
|
||||||
'achievements.habitBirthday':{$exists:false}
|
'achievements.habitBirthdays':1,
|
||||||
};
|
'auth.timestamps.loggedin':{$gt:new Date('2014-12-20')}
|
||||||
|
};
|
||||||
|
|
||||||
|
// '_id': 'c03e41bd-501f-438c-9553-a7afdf52a08c',
|
||||||
|
// 'achievements.habitBirthday':{$exists:false},
|
||||||
|
// 'items.gear.owned.armor_special_birthday2015':1
|
||||||
|
|
||||||
var fields = {
|
var fields = {
|
||||||
'items.gear.owned.armor_special_birthday2015':1
|
// 'auth.timestamps.created':1,
|
||||||
|
// 'achievements.habitBirthday':1,
|
||||||
|
// 'achievements.habitBirthdays':1,
|
||||||
|
'items.gear.owned.armor_special_birthday2015':1,
|
||||||
|
// 'items.gear.owned.armor_special':1
|
||||||
};
|
};
|
||||||
|
|
||||||
console.warn('Updating users...');
|
console.warn('Updating users...');
|
||||||
@@ -33,9 +43,11 @@ dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
|||||||
count++;
|
count++;
|
||||||
|
|
||||||
var unset = {'items.gear.owned.armor_special_birthday2015': 1};
|
var unset = {'items.gear.owned.armor_special_birthday2015': 1};
|
||||||
var set = {'migration': migrationName};
|
// var set = {'migration':migrationName, 'achievements.habitBirthdays':1 };
|
||||||
// var inc = {'xyz':1, _v:1};
|
// var inc = {'xyz':1, _v:1};
|
||||||
dbUsers.update({_id:user._id}, {$unset:unset, $set:set}); // , $inc:inc});
|
dbUsers.update({_id:user._id}, {$unset:unset}); // , $inc:inc});
|
||||||
|
// dbUsers.update({_id:user._id}, {$unset:unset, $set:set});
|
||||||
|
// console.warn(user.auth.timestamps.created);
|
||||||
|
|
||||||
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
|
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
|
||||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
var migrationName = '20150201_convert_creation_date_from_string_to_object__no_date_recent_signup';
|
||||||
|
//// var migrationName = '20150201_convert_creation_date_from_string_to_object';
|
||||||
|
|
||||||
|
var authorName = 'Alys'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For users that have no value for auth.timestamps.created, assign them
|
||||||
|
* a recent value.
|
||||||
|
*
|
||||||
|
* NOTE:
|
||||||
|
* Before this script was used as described above, it was first used to
|
||||||
|
* find all users that have a auth.timestamps.created field that is a string
|
||||||
|
* rather than a date object and set it to be a date object. The code used
|
||||||
|
* for this has been commented out with four slashes: ////
|
||||||
|
*
|
||||||
|
* https://github.com/HabitRPG/habitrpg/issues/4601#issuecomment-72339846
|
||||||
|
*/
|
||||||
|
|
||||||
|
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
|
||||||
|
|
||||||
|
var mongo = require('mongoskin');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var moment = require('moment');
|
||||||
|
|
||||||
|
var dbUsers = mongo.db(dbserver + '/habitrpg?auto_reconnect').collection('users');
|
||||||
|
|
||||||
|
var uuidArrayRecent=[ // recent users with no creation dates
|
||||||
|
'1a0d4b75-73ed-4937-974d-d504d6398884',
|
||||||
|
'1c7ebe27-1250-4f95-ba10-965580adbfd7',
|
||||||
|
'5f972121-4a6d-411c-95e9-7093d3e89b66',
|
||||||
|
'ae85818a-e336-4ccd-945e-c15cef975102',
|
||||||
|
'ba273976-d9fc-466c-975f-38559d34a824',
|
||||||
|
];
|
||||||
|
|
||||||
|
var query = {
|
||||||
|
'_id':{$in: uuidArrayRecent}
|
||||||
|
//// 'auth':{$exists:true},
|
||||||
|
//// 'auth.timestamps':{$exists:true},
|
||||||
|
//// 'auth.timestamps.created':{$not: {$lt:new Date('2018-01-01')}}
|
||||||
|
};
|
||||||
|
|
||||||
|
var fields = {
|
||||||
|
'_id':1,
|
||||||
|
'auth.timestamps.created':1
|
||||||
|
};
|
||||||
|
// 'achievements.habitBirthdays':1
|
||||||
|
|
||||||
|
console.warn('Updating users...');
|
||||||
|
var progressCount = 1000;
|
||||||
|
var count = 0;
|
||||||
|
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||||
|
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||||
|
if (!user) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
return displayData();
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
|
||||||
|
//// var oldDate = user.auth.timestamps.created;
|
||||||
|
//// var newDate = moment(oldDate).toDate();
|
||||||
|
var oldDate = 'none';
|
||||||
|
var newDate = moment('2015-01-11').toDate();
|
||||||
|
console.warn(user._id + ' == ' + oldDate + ' == ' + newDate);
|
||||||
|
|
||||||
|
//// var set = { 'migration': migrationName,
|
||||||
|
//// 'auth.timestamps.created': newDate,
|
||||||
|
//// 'achievements.habitBirthdays': 2,
|
||||||
|
//// 'items.gear.owned.head_special_nye':true,
|
||||||
|
//// 'items.gear.owned.head_special_nye2014':true,
|
||||||
|
//// 'items.gear.owned.armor_special_birthday':true,
|
||||||
|
//// 'items.gear.owned.armor_special_birthday2015':true,
|
||||||
|
//// };
|
||||||
|
|
||||||
|
var set = { 'migration': migrationName,
|
||||||
|
'auth.timestamps.created': newDate,
|
||||||
|
'achievements.habitBirthdays': 1,
|
||||||
|
'items.gear.owned.armor_special_birthday':true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// var unset = {'items.gear.owned.armor_special_birthday2015': 1};
|
||||||
|
// var inc = {'xyz':1, _v:1};
|
||||||
|
dbUsers.update({_id:user._id}, {$set:set});
|
||||||
|
// dbUsers.update({_id:user._id}, {$unset:unset, $set:set, $inc:inc});
|
||||||
|
|
||||||
|
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
|
||||||
|
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||||
|
if (user._id == '9' ) console.warn('lefnire' + ' processed');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function displayData() {
|
||||||
|
console.warn('\n' + count + ' users processed\n');
|
||||||
|
return exiting(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function exiting(code, msg) {
|
||||||
|
code = code || 0; // 0 = success
|
||||||
|
if (code && !msg) { msg = 'ERROR!'; }
|
||||||
|
if (msg) {
|
||||||
|
if (code) { console.error(msg); }
|
||||||
|
else { console.log( msg); }
|
||||||
|
}
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
@@ -213,6 +213,10 @@ window.habitrpg = angular.module('habitrpg',
|
|||||||
url: "/subscription",
|
url: "/subscription",
|
||||||
templateUrl: "partials/options.settings.subscription.html"
|
templateUrl: "partials/options.settings.subscription.html"
|
||||||
})
|
})
|
||||||
|
.state('options.settings.notifications', {
|
||||||
|
url: "/notifications",
|
||||||
|
templateUrl: "partials/options.settings.notifications.html"
|
||||||
|
})
|
||||||
|
|
||||||
var settings = JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID));
|
var settings = JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID));
|
||||||
if (settings && settings.auth) {
|
if (settings && settings.auth) {
|
||||||
|
|||||||
@@ -242,5 +242,14 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
|
|||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
window.location.reload(false);
|
window.location.reload(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Universal method for sending HTTP methods
|
||||||
|
$rootScope.http = function(method, route, data, alertMsg){
|
||||||
|
$http[method](ApiUrl.get() + route, data).success(function(){
|
||||||
|
if (alertMsg) Notification.text(window.env.t(alertMsg));
|
||||||
|
User.sync();
|
||||||
|
});
|
||||||
|
// error will be handled via $http interceptor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -77,33 +77,12 @@ habitrpg.controller('SettingsCtrl',
|
|||||||
$rootScope.$state.go('tasks');
|
$rootScope.$state.go('tasks');
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.changeUsername = function(changeUser){
|
$scope.changeUser = function(attr, updates){
|
||||||
if (!changeUser.newUsername || !changeUser.password) {
|
$http.post(ApiUrl.get() + '/api/v2/user/change-'+attr, updates)
|
||||||
return alert(window.env.t('fillAll'));
|
|
||||||
}
|
|
||||||
$http.post(ApiUrl.get() + '/api/v2/user/change-username', changeUser)
|
|
||||||
.success(function(){
|
.success(function(){
|
||||||
alert(window.env.t('usernameSuccess'));
|
alert(window.env.t(attr+'Success'));
|
||||||
$scope.changeUser = {};
|
_.each(updates, function(v,k){updates[k]=null;});
|
||||||
User.sync();
|
User.sync();
|
||||||
})
|
|
||||||
.error(function(data){
|
|
||||||
alert(data.err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.changePassword = function(changePass){
|
|
||||||
if (!changePass.oldPassword || !changePass.newPassword || !changePass.confirmNewPassword) {
|
|
||||||
return alert(window.env.t('fillAll'));
|
|
||||||
}
|
|
||||||
$http.post(ApiUrl.get() + '/api/v2/user/change-password', changePass)
|
|
||||||
.success(function(data, status, headers, config){
|
|
||||||
if (data.err) return alert(data.err);
|
|
||||||
alert(window.env.t('passSuccess'));
|
|
||||||
$scope.changePass = {};
|
|
||||||
})
|
|
||||||
.error(function(data, status, headers, config){
|
|
||||||
alert(data.err);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ habitrpg
|
|||||||
link: function(scope, element, attrs) {
|
link: function(scope, element, attrs) {
|
||||||
// $scope.obj needs to come from controllers, so we can pass by ref
|
// $scope.obj needs to come from controllers, so we can pass by ref
|
||||||
scope.main = attrs.main;
|
scope.main = attrs.main;
|
||||||
|
scope.modal = attrs.modal;
|
||||||
var dailiesView;
|
var dailiesView;
|
||||||
if(User.user.preferences.dailyDueDefaultView) {
|
if(User.user.preferences.dailyDueDefaultView) {
|
||||||
dailiesView = "remaining";
|
dailiesView = "remaining";
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ var accountSuspended = function(uuid){
|
|||||||
code: 'ACCOUNT_SUSPENDED'
|
code: 'ACCOUNT_SUSPENDED'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// escape email for regex, then search case-insensitive. See http://stackoverflow.com/a/3561711/362790
|
||||||
|
var mongoEmailRegex = function(email){
|
||||||
|
return new RegExp('^' + email.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '$', 'i');
|
||||||
|
}
|
||||||
|
|
||||||
api.auth = function(req, res, next) {
|
api.auth = function(req, res, next) {
|
||||||
var uid = req.headers['x-api-user'];
|
var uid = req.headers['x-api-user'];
|
||||||
@@ -61,55 +65,57 @@ api.authWithUrl = function(req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
api.registerUser = function(req, res, next) {
|
api.registerUser = function(req, res, next) {
|
||||||
var confirmPassword = req.body.confirmPassword,
|
async.auto({
|
||||||
email = req.body.email,
|
validate: function(cb) {
|
||||||
password = req.body.password,
|
if (!(req.body.username && req.body.password && req.body.email))
|
||||||
username = req.body.username;
|
return cb({code:401, err: ":username, :email, :password, :confirmPassword required"});
|
||||||
if (!(username && password && email)) return res.json(401, {err: ":username, :email, :password, :confirmPassword required"});
|
if (req.body.password !== req.body.confirmPassword)
|
||||||
if (password !== confirmPassword) return res.json(401, {err: ":password and :confirmPassword don't match"});
|
return cb({code:401, err: ":password and :confirmPassword don't match"});
|
||||||
if (!validator.isEmail(email)) return res.json(401, {err: ":email invalid"});
|
if (!validator.isEmail(req.body.email))
|
||||||
async.waterfall([
|
return cb({code:401, err: ":email invalid"});
|
||||||
function(cb) {
|
cb();
|
||||||
User.findOne({'auth.local.email': email}, cb);
|
|
||||||
},
|
},
|
||||||
function(found, cb) {
|
findEmail: function(cb) {
|
||||||
if (found) return cb("Email already taken");
|
User.findOne({'auth.local.email': req.body.email}, cb);
|
||||||
User.findOne({'auth.local.username': username}, cb);
|
},
|
||||||
}, function(found, cb) {
|
findUname: function(cb) {
|
||||||
var newUser, salt, user;
|
User.findOne({'auth.local.username': req.body.username}, cb);
|
||||||
if (found) return cb("Username already taken");
|
},
|
||||||
salt = utils.makeSalt();
|
findFacebook: function(cb){
|
||||||
newUser = {
|
User.findOne({_id: req.headers['x-api-user'], apiToken: req.headers['x-api-key']}, {auth:1}, cb);
|
||||||
|
},
|
||||||
|
register: ['validate', 'findEmail', 'findUname', 'findFacebook', function(cb, data) {
|
||||||
|
if (data.findEmail) return cb({code:401, err:"Email already taken"});
|
||||||
|
if (data.findUname) return cb({code:401, err:"Username already taken"});
|
||||||
|
var salt = utils.makeSalt();
|
||||||
|
var newUser = {
|
||||||
auth: {
|
auth: {
|
||||||
local: {
|
local: {
|
||||||
username: username,
|
username: req.body.username,
|
||||||
email: email,
|
email: req.body.email,
|
||||||
salt: salt,
|
salt: salt,
|
||||||
hashed_password: utils.encryptPassword(password, salt)
|
hashed_password: utils.encryptPassword(req.body.password, salt)
|
||||||
},
|
},
|
||||||
timestamps: {created: +new Date(), loggedIn: +new Date()}
|
timestamps: {created: +new Date(), loggedIn: +new Date()}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
newUser.preferences = newUser.preferences || {};
|
// existing user, allow them to add local authentication
|
||||||
newUser.preferences.language = req.language; // User language detected from browser, not saved
|
if (data.findFacebook) {
|
||||||
user = new User(newUser);
|
data.findFacebook.auth.local = newUser.auth.local;
|
||||||
|
data.findFacebook.save(cb);
|
||||||
// temporary for conventions
|
// new user, register them
|
||||||
if (req.subdomains[0] == 'con') {
|
} else {
|
||||||
_.each(user.dailys, function(h){
|
newUser.preferences = newUser.preferences || {};
|
||||||
h.repeat = {m:false,t:false,w:false,th:false,f:false,s:false,su:false};
|
newUser.preferences.language = req.language; // User language detected from browser, not saved
|
||||||
})
|
var user = new User(newUser);
|
||||||
user.extra = {signupEvent: 'wondercon'};
|
utils.txnEmail(user, 'welcome');
|
||||||
|
ga.event('register', 'Local').send();
|
||||||
|
user.save(cb);
|
||||||
}
|
}
|
||||||
|
}]
|
||||||
user.save(cb);
|
}, function(err, data) {
|
||||||
if(isProd) utils.txnEmail({name:username, email:email}, 'welcome');
|
if (err) return err.code ? res.json(err.code, err) : next(err);
|
||||||
ga.event('register', 'Local').send()
|
res.json(200, data.register[0]);
|
||||||
}
|
|
||||||
], function(err, saved) {
|
|
||||||
if (err) return res.json(401, {err: err});
|
|
||||||
res.json(200, saved);
|
|
||||||
email = password = username = null;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -173,9 +179,7 @@ api.loginSocial = function(req, res, next) {
|
|||||||
user = new User(user);
|
user = new User(user);
|
||||||
user.save(cb);
|
user.save(cb);
|
||||||
|
|
||||||
if (isProd && prof.emails && prof.emails[0] && prof.emails[0].value) {
|
utils.txnEmail(user, 'welcome');
|
||||||
utils.txnEmail({name: prof.displayName || prof.username, email: prof.emails[0].value}, 'welcome');
|
|
||||||
}
|
|
||||||
ga.event('register', network).send();
|
ga.event('register', network).send();
|
||||||
}]
|
}]
|
||||||
}, function(err, results){
|
}, function(err, results){
|
||||||
@@ -188,9 +192,18 @@ api.loginSocial = function(req, res, next) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE /user/auth/social
|
* DELETE /user/auth/social
|
||||||
* TODO implement
|
|
||||||
*/
|
*/
|
||||||
api.deleteSocial = function(req,res,next){next()}
|
api.deleteSocial = function(req,res,next){
|
||||||
|
if (!res.locals.user.auth.local.username)
|
||||||
|
return res.json(401, {err:"Account lacks another authentication method, can't detach Facebook"});
|
||||||
|
//FIXME for some reason, the following gives https://gist.github.com/lefnire/f93eb306069b9089d123
|
||||||
|
//res.locals.user.auth.facebook = null;
|
||||||
|
//res.locals.user.auth.save(function(err, saved){
|
||||||
|
User.update({_id:res.locals.user._id}, {$unset:{'auth.facebook':1}}, function(err){
|
||||||
|
if (err) return next(err);
|
||||||
|
res.send(200);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
api.resetPassword = function(req, res, next){
|
api.resetPassword = function(req, res, next){
|
||||||
var email = req.body.email,
|
var email = req.body.email,
|
||||||
@@ -198,9 +211,7 @@ api.resetPassword = function(req, res, next){
|
|||||||
newPassword = utils.makeSalt(), // use a salt as the new password too (they'll change it later)
|
newPassword = utils.makeSalt(), // use a salt as the new password too (they'll change it later)
|
||||||
hashed_password = utils.encryptPassword(newPassword, salt);
|
hashed_password = utils.encryptPassword(newPassword, salt);
|
||||||
|
|
||||||
// escape email for regex, then search case-insensitive. See http://stackoverflow.com/a/3561711/362790
|
User.findOne({'auth.local.email':mongoEmailRegex(email)}, function(err, user){
|
||||||
var emailRegExp = new RegExp('^' + email.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '$', 'i');
|
|
||||||
User.findOne({'auth.local.email':emailRegExp}, function(err, user){
|
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (!user) return res.send(500, {err:"Couldn't find a user registered for email " + email});
|
if (!user) return res.send(500, {err:"Couldn't find a user registered for email " + email});
|
||||||
user.auth.local.salt = salt;
|
user.auth.local.salt = salt;
|
||||||
@@ -217,28 +228,45 @@ api.resetPassword = function(req, res, next){
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var invalidPassword = function(user, password){
|
||||||
|
var hashed_password = utils.encryptPassword(password, user.auth.local.salt);
|
||||||
|
if (hashed_password !== user.auth.local.hashed_password)
|
||||||
|
return {code:401, err:"Incorrect password"};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
api.changeUsername = function(req, res, next) {
|
api.changeUsername = function(req, res, next) {
|
||||||
var user = res.locals.user,
|
async.waterfall([
|
||||||
password = req.body.password,
|
function(cb){
|
||||||
newUsername = req.body.newUsername;
|
User.findOne({'auth.local.username': req.body.username}, {auth:1}, cb);
|
||||||
|
},
|
||||||
|
function(found, cb){
|
||||||
|
if (found) return cb({code:401, err: "Username already taken"});
|
||||||
|
if (invalidPassword(res.locals.user, req.body.password)) return cb(invalidPassword(res.locals.user, req.body.password));
|
||||||
|
res.locals.user.auth.local.username = req.body.username;
|
||||||
|
res.locals.user.save(cb);
|
||||||
|
}
|
||||||
|
], function(err){
|
||||||
|
if (err) return err.code ? res.json(err.code, err) : next(err);
|
||||||
|
res.send(200);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
User.findOne({'auth.local.username': newUsername}, function(err, result) {
|
api.changeEmail = function(req, res, next){
|
||||||
if (err) next(err);
|
async.waterfall([
|
||||||
if(result) return res.json(401, {err: "Username already taken"});
|
function(cb){
|
||||||
|
User.findOne({'auth.local.email': mongoEmailRegex(req.body.email)}, {auth:1}, cb);
|
||||||
var salt = user.auth.local.salt;
|
},
|
||||||
var hashed_password = utils.encryptPassword(password, salt);
|
function(found, cb){
|
||||||
|
if(found) return cb({code:401, err: "Email already taken"});
|
||||||
if (hashed_password !== user.auth.local.hashed_password)
|
if (invalidPassword(res.locals.user, req.body.password)) return cb(invalidPassword(res.locals.user, req.body.password));
|
||||||
return res.json(401, {err:"Incorrect password"});
|
res.locals.user.auth.local.email = req.body.email;
|
||||||
|
res.locals.user.save(cb);
|
||||||
user.auth.local.username = newUsername;
|
}
|
||||||
user.save(function(err, saved){
|
], function(err){
|
||||||
if (err) next(err);
|
if (err) return err.code ? res.json(err.code,err) : next(err);
|
||||||
res.send(200);
|
res.send(200);
|
||||||
user = password = newUsername = null;
|
})
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
api.changePassword = function(req, res, next) {
|
api.changePassword = function(req, res, next) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ var Group = require('./../models/group').model;
|
|||||||
var Challenge = require('./../models/challenge').model;
|
var Challenge = require('./../models/challenge').model;
|
||||||
var logging = require('./../logging');
|
var logging = require('./../logging');
|
||||||
var csv = require('express-csv');
|
var csv = require('express-csv');
|
||||||
|
var utils = require('../utils');
|
||||||
var api = module.exports;
|
var api = module.exports;
|
||||||
|
|
||||||
|
|
||||||
@@ -335,6 +336,11 @@ api.selectWinner = function(req, res, next) {
|
|||||||
winner.save(cb);
|
winner.save(cb);
|
||||||
},
|
},
|
||||||
function(saved, num, cb) {
|
function(saved, num, cb) {
|
||||||
|
if(saved.preferences.emailNotifications.wonChallenge !== false){
|
||||||
|
utils.txnEmail(saved, 'won-challenge', [
|
||||||
|
{name: 'CHALLENGE_NAME', content: chal.name}
|
||||||
|
]);
|
||||||
|
}
|
||||||
closeChal(cid, {broken: 'CHALLENGE_CLOSED', winner: saved.profile.name}, cb);
|
closeChal(cid, {broken: 'CHALLENGE_CLOSED', winner: saved.profile.name}, cb);
|
||||||
}
|
}
|
||||||
], function(err){
|
], function(err){
|
||||||
|
|||||||
@@ -301,13 +301,11 @@ api.flagChatMessage = function(req, res, next){
|
|||||||
group.markModified('chat');
|
group.markModified('chat');
|
||||||
group.save(function(err,_saved){
|
group.save(function(err,_saved){
|
||||||
if(err) return next(err);
|
if(err) return next(err);
|
||||||
if (isProd){
|
|
||||||
|
|
||||||
var addressesToSendTo = JSON.parse(nconf.get('FLAG_REPORT_EMAIL'));
|
var addressesToSendTo = JSON.parse(nconf.get('FLAG_REPORT_EMAIL'));
|
||||||
|
|
||||||
if(Array.isArray(addressesToSendTo)){
|
if(Array.isArray(addressesToSendTo)){
|
||||||
addressesToSendTo = addressesToSendTo.map(function(email){
|
addressesToSendTo = addressesToSendTo.map(function(email){
|
||||||
return {email: email}
|
return {email: email, canSend: true}
|
||||||
});
|
});
|
||||||
}else{
|
}else{
|
||||||
addressesToSendTo = {email: addressesToSendTo}
|
addressesToSendTo = {email: addressesToSendTo}
|
||||||
@@ -332,7 +330,7 @@ api.flagChatMessage = function(req, res, next){
|
|||||||
{name: "GROUP_ID", content: group._id},
|
{name: "GROUP_ID", content: group._id},
|
||||||
{name: "GROUP_URL", content: group._id == 'habitrpg' ? (nconf.get('BASE_URL') + '/#/options/groups/tavern') : (group.type === 'guild' ? (nconf.get('BASE_URL')+ '/#/options/groups/guilds/' + group._id) : 'party')},
|
{name: "GROUP_URL", content: group._id == 'habitrpg' ? (nconf.get('BASE_URL') + '/#/options/groups/tavern') : (group.type === 'guild' ? (nconf.get('BASE_URL')+ '/#/options/groups/guilds/' + group._id) : 'party')},
|
||||||
]);
|
]);
|
||||||
}
|
|
||||||
return res.send(204);
|
return res.send(204);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -556,6 +554,26 @@ api.invite = function(req, res, next) {
|
|||||||
], function(err, results){
|
], function(err, results){
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
|
if(invite.preferences.emailNotifications['invited' + (group.type == 'guild' ? 'Guild' : 'Party')] !== false){
|
||||||
|
var emailVars = [
|
||||||
|
{name: 'INVITER', content: utils.getUserInfo(res.locals.user, ['name']).name}
|
||||||
|
];
|
||||||
|
|
||||||
|
if(group.type == 'guild'){
|
||||||
|
emailVars.push(
|
||||||
|
{name: 'GUILD_NAME', content: group.name},
|
||||||
|
{name: 'GUILD_URL', content: nconf.get('BASE_URL') + '/#/options/groups/guilds/public'}
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
emailVars.push(
|
||||||
|
{name: 'PARTY_NAME', content: group.name},
|
||||||
|
{name: 'PARTY_URL', content: nconf.get('BASE_URL') + '/#/options/groups/party'}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.txnEmail(invite, ('invited-' + (group.type == 'guild' ? 'guild' : 'party')), emailVars);
|
||||||
|
}
|
||||||
|
|
||||||
// Have to return whole group and its members for angular to show the invited user
|
// Have to return whole group and its members for angular to show the invited user
|
||||||
res.json(results[2]);
|
res.json(results[2]);
|
||||||
group = uuid = null;
|
group = uuid = null;
|
||||||
@@ -629,7 +647,7 @@ questStart = function(req, res, next) {
|
|||||||
var group = res.locals.group;
|
var group = res.locals.group;
|
||||||
var force = req.query.force;
|
var force = req.query.force;
|
||||||
|
|
||||||
// if (group.quest.active) return res.json(400,{err:'Quest already began.'});
|
// if (group.quest.active) return res.json(400,{err:'Quest already began.'});
|
||||||
// temporarily send error email, until we know more about this issue (then remove below, uncomment above).
|
// temporarily send error email, until we know more about this issue (then remove below, uncomment above).
|
||||||
if (group.quest.active) return next('Quest already began.');
|
if (group.quest.active) return next('Quest already began.');
|
||||||
|
|
||||||
@@ -720,8 +738,9 @@ api.questAccept = function(req, res, next) {
|
|||||||
if (m == user._id) {
|
if (m == user._id) {
|
||||||
group.quest.members[m] = true;
|
group.quest.members[m] = true;
|
||||||
group.quest.leader = user._id;
|
group.quest.leader = user._id;
|
||||||
} else
|
} else {
|
||||||
group.quest.members[m] = undefined;
|
group.quest.members[m] = undefined;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Party member accepting the invitation
|
// Party member accepting the invitation
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ var api = module.exports;
|
|||||||
var async = require('async');
|
var async = require('async');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var shared = require('../../../common');
|
var shared = require('../../../common');
|
||||||
|
var utils = require('../utils');
|
||||||
|
var nconf = require('nconf');
|
||||||
|
|
||||||
var fetchMember = function(uuid, restrict){
|
var fetchMember = function(uuid, restrict){
|
||||||
return function(cb){
|
return function(cb){
|
||||||
@@ -48,9 +50,11 @@ api.sendMessage = function(user, member, data){
|
|||||||
}
|
}
|
||||||
|
|
||||||
api.sendPrivateMessage = function(req, res, next){
|
api.sendPrivateMessage = function(req, res, next){
|
||||||
|
var fetchedMember;
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
fetchMember(req.params.uuid),
|
fetchMember(req.params.uuid),
|
||||||
function(member, cb) {
|
function(member, cb) {
|
||||||
|
fetchedMember = member;
|
||||||
if (~member.inbox.blocks.indexOf(res.locals.user._id) // can't send message if that user blocked me
|
if (~member.inbox.blocks.indexOf(res.locals.user._id) // can't send message if that user blocked me
|
||||||
|| ~res.locals.user.inbox.blocks.indexOf(member._id) // or if I blocked them
|
|| ~res.locals.user.inbox.blocks.indexOf(member._id) // or if I blocked them
|
||||||
|| member.inbox.optOut) { // or if they've opted out of messaging
|
|| member.inbox.optOut) { // or if they've opted out of messaging
|
||||||
@@ -64,6 +68,14 @@ api.sendPrivateMessage = function(req, res, next){
|
|||||||
}
|
}
|
||||||
], function(err){
|
], function(err){
|
||||||
if (err) return sendErr(err, res, next);
|
if (err) return sendErr(err, res, next);
|
||||||
|
|
||||||
|
if(fetchedMember.preferences.emailNotifications.newPM !== false){
|
||||||
|
utils.txnEmail(fetchedMember, 'new-pm', [
|
||||||
|
{name: 'SENDER', content: utils.getUserInfo(res.locals.user, ['name']).name},
|
||||||
|
{name: 'PMS_INBOX_URL', content: nconf.get('BASE_URL') + '/#/options/groups/inbox'}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
res.send(200);
|
res.send(200);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -84,6 +96,12 @@ api.sendGift = function(req, res, next){
|
|||||||
member.balance += amt;
|
member.balance += amt;
|
||||||
user.balance -= amt;
|
user.balance -= amt;
|
||||||
api.sendMessage(user, member, req.body);
|
api.sendMessage(user, member, req.body);
|
||||||
|
if(member.preferences.emailNotifications.giftedGems !== false){
|
||||||
|
utils.txnEmail(member, 'gifted-gems', [
|
||||||
|
{name: 'GIFTER', content: utils.getUserInfo(user, ['name']).name},
|
||||||
|
{name: 'X_GEMS_GIFTED', content: req.body.gems.amount}
|
||||||
|
]);
|
||||||
|
}
|
||||||
return async.parallel([
|
return async.parallel([
|
||||||
function (cb2) { member.save(cb2) },
|
function (cb2) { member.save(cb2) },
|
||||||
function (cb2) { user.save(cb2) }
|
function (cb2) { user.save(cb2) }
|
||||||
|
|||||||
@@ -73,7 +73,15 @@ exports.createSubscription = function(data, cb) {
|
|||||||
utils.ga.transaction(data.user._id, block.price).item(block.price, 1, data.paymentMethod.toLowerCase() + '-subscription', data.paymentMethod).send();
|
utils.ga.transaction(data.user._id, block.price).item(block.price, 1, data.paymentMethod.toLowerCase() + '-subscription', data.paymentMethod).send();
|
||||||
}
|
}
|
||||||
data.user.purchased.txnCount++;
|
data.user.purchased.txnCount++;
|
||||||
if (data.gift) members.sendMessage(data.user, data.gift.member, data.gift);
|
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', [
|
||||||
|
{name: 'GIFTER', content: utils.getUserInfo(data.user, ['name']).name},
|
||||||
|
{name: 'X_MONTHS_SUBSCRIPTION', content: months}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
async.parallel([
|
async.parallel([
|
||||||
function(cb2){data.user.save(cb2)},
|
function(cb2){data.user.save(cb2)},
|
||||||
function(cb2){data.gift ? data.gift.member.save(cb2) : cb2(null);}
|
function(cb2){data.gift ? data.gift.member.save(cb2) : cb2(null);}
|
||||||
@@ -96,7 +104,7 @@ exports.cancelSubscription = function(data, cb) {
|
|||||||
p.extraMonths = 0; // clear extra time. If they subscribe again, it'll be recalculated from p.dateTerminated
|
p.extraMonths = 0; // clear extra time. If they subscribe again, it'll be recalculated from p.dateTerminated
|
||||||
|
|
||||||
data.user.save(cb);
|
data.user.save(cb);
|
||||||
if(isProduction) utils.txnEmail(data.user, 'cancel-subscription');
|
utils.txnEmail(data.user, 'cancel-subscription');
|
||||||
utils.ga.event('unsubscribe', data.paymentMethod).send();
|
utils.ga.event('unsubscribe', data.paymentMethod).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +118,15 @@ exports.buyGems = function(data, cb) {
|
|||||||
//TODO ga.transaction to reflect whether this is gift or self-purchase
|
//TODO ga.transaction to reflect whether this is gift or self-purchase
|
||||||
utils.ga.transaction(data.user._id, amt).item(amt, 1, data.paymentMethod.toLowerCase() + "-checkout", "Gems > " + data.paymentMethod).send();
|
utils.ga.transaction(data.user._id, amt).item(amt, 1, data.paymentMethod.toLowerCase() + "-checkout", "Gems > " + data.paymentMethod).send();
|
||||||
}
|
}
|
||||||
if (data.gift) members.sendMessage(data.user, data.gift.member, data.gift);
|
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', [
|
||||||
|
{name: 'GIFTER', content: utils.getUserInfo(data.user, ['name']).name},
|
||||||
|
{name: 'X_GEMS_GIFTED', content: data.gift.gems.amount || 20}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
async.parallel([
|
async.parallel([
|
||||||
function(cb2){data.user.save(cb2)},
|
function(cb2){data.user.save(cb2)},
|
||||||
function(cb2){data.gift ? data.gift.member.save(cb2) : cb2(null);}
|
function(cb2){data.gift ? data.gift.member.save(cb2) : cb2(null);}
|
||||||
@@ -137,4 +153,4 @@ exports.paypalCheckoutSuccess = paypal.executePayment;
|
|||||||
exports.paypalIPN = paypal.ipn;
|
exports.paypalIPN = paypal.ipn;
|
||||||
|
|
||||||
exports.iapAndroidVerify = iap.androidVerify;
|
exports.iapAndroidVerify = iap.androidVerify;
|
||||||
exports.iapIosVerify = iap.iosVerify;
|
exports.iapIosVerify = iap.iosVerify;
|
||||||
@@ -261,58 +261,36 @@ api.update = function(req, res, next) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
api.cron = function(req, res, next) {
|
api.cron = function(req, res, next) {
|
||||||
try{
|
var user = res.locals.user,
|
||||||
var user = res.locals.user,
|
progress = user.fns.cron(),
|
||||||
progress = user.fns.cron(),
|
ranCron = user.isModified(),
|
||||||
ranCron = user.isModified(),
|
quest = shared.content.quests[user.party.quest.key];
|
||||||
quest = shared.content.quests[user.party.quest.key];
|
|
||||||
|
|
||||||
if (ranCron) res.locals.wasModified = true;
|
if (ranCron) res.locals.wasModified = true;
|
||||||
if (!ranCron) return next(null,user);
|
if (!ranCron) return next(null,user);
|
||||||
Group.tavernBoss(user,progress);
|
Group.tavernBoss(user,progress);
|
||||||
if (!quest) return user.save(next);
|
if (!quest) return user.save(next);
|
||||||
|
|
||||||
// FOR DEBUGGING, PLEASE IGNORE
|
|
||||||
var opStatus = 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){
|
|
||||||
opStatus = 'saveUser';
|
|
||||||
user.save(cb); // make sure to save the cron effects
|
|
||||||
},
|
|
||||||
function(saved, count, cb){
|
|
||||||
opStatus = 'runQuest';
|
|
||||||
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: "Cron caught",
|
|
||||||
stack: (err.stack || err.message || err),
|
|
||||||
body: req.body, headers: req.header,
|
|
||||||
auth: req.headers['x-api-user'],
|
|
||||||
originalUrl: req.originalUrl,
|
|
||||||
opStatus: opStatus
|
|
||||||
});
|
|
||||||
res.locals.user = saved;
|
|
||||||
next(err,saved);
|
|
||||||
user = progress = quest = null;
|
|
||||||
});
|
|
||||||
}catch(e){
|
|
||||||
logging.loggly({
|
|
||||||
error: "Cron uncaught",
|
|
||||||
stack: e.stack || e
|
|
||||||
});
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// api.reroll // Shared.ops
|
// api.reroll // Shared.ops
|
||||||
@@ -437,16 +415,34 @@ api.cast = function(req, res, next) {
|
|||||||
api.inviteFriends = function(req, res, next) {
|
api.inviteFriends = function(req, res, next) {
|
||||||
Group.findOne({type:'party', members:{'$in': [res.locals.user._id]}}).select('_id name').exec(function(err,party){
|
Group.findOne({type:'party', members:{'$in': [res.locals.user._id]}}).select('_id name').exec(function(err,party){
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
var link = nconf.get('BASE_URL')+'?partyInvite='+ utils.encrypt(JSON.stringify({id:party._id, inviter:res.locals.user._id, name:party.name}));
|
|
||||||
_.each(req.body.emails, function(invite){
|
_.each(req.body.emails, function(invite){
|
||||||
if (invite.email) {
|
if (invite.email) {
|
||||||
var variables = [
|
|
||||||
{name: 'LINK', content: link},
|
User.findOne({$or: [
|
||||||
{name: 'INVITER', content: req.body.inviter || res.locals.user.profile.name},
|
{'auth.local.email': invite.email},
|
||||||
{name: 'INVITEE', content: invite.name}
|
{'auth.facebook.emails.value': invite.email}
|
||||||
];
|
]}).select({_id: true, 'preferences.emailNotifications': true})
|
||||||
// TODO implement "users can only be invited once"
|
.exec(function(err, userToContact){
|
||||||
utils.txnEmail(invite, 'invite-friend', variables);
|
if(err) return next(err);
|
||||||
|
|
||||||
|
var link = nconf.get('BASE_URL')+'?partyInvite='+ utils.encrypt(JSON.stringify({id:party._id, inviter:res.locals.user._id, name:party.name}));
|
||||||
|
|
||||||
|
var variables = [
|
||||||
|
{name: 'LINK', content: link},
|
||||||
|
{name: 'INVITER', content: req.body.inviter || utils.getUserInfo(res.locals.user, ['name']).name}
|
||||||
|
];
|
||||||
|
|
||||||
|
invite.canSend = true;
|
||||||
|
|
||||||
|
// We check for unsubscribeFromAll here because don't pass through utils.getUserInfo
|
||||||
|
if(!userToContact || (userToContact.preferences.emailNotifications.invitedParty !== false &&
|
||||||
|
userToContact.preferences.emailNotifications.unsubscribeFromAll !== true)){
|
||||||
|
// TODO implement "users can only be invited once"
|
||||||
|
utils.txnEmail(invite, 'invite-friend', variables);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
res.send(200);
|
res.send(200);
|
||||||
@@ -477,7 +473,7 @@ api.sessionPartyInvite = function(req,res,next){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All other user.ops which can easily be mapped to ../../common/scripts/index.coffee, not requiring custom API-wrapping
|
* All other user.ops which can easily be mapped to habitrpg-shared/index.coffee, not requiring custom API-wrapping
|
||||||
*/
|
*/
|
||||||
_.each(shared.wrap({}).ops, function(op,k){
|
_.each(shared.wrap({}).ops, function(op,k){
|
||||||
if (!api[k]) {
|
if (!api[k]) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ require('winston-newrelic');
|
|||||||
|
|
||||||
var logger, loggly;
|
var logger, loggly;
|
||||||
|
|
||||||
|
// Currently disabled
|
||||||
if (nconf.get('LOGGLY:enabled')){
|
if (nconf.get('LOGGLY:enabled')){
|
||||||
loggly = require('loggly').createClient({
|
loggly = require('loggly').createClient({
|
||||||
token: nconf.get('LOGGLY:token'),
|
token: nconf.get('LOGGLY:token'),
|
||||||
|
|||||||
@@ -75,13 +75,13 @@ module.exports.errorHandler = function(err, req, res, next) {
|
|||||||
"\n\nbody: " + JSON.stringify(req.body) +
|
"\n\nbody: " + JSON.stringify(req.body) +
|
||||||
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
|
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
|
||||||
logging.error(stack);
|
logging.error(stack);
|
||||||
logging.loggly({
|
/*logging.loggly({
|
||||||
error: "Uncaught error",
|
error: "Uncaught error",
|
||||||
stack: (err.stack || err.message || err),
|
stack: (err.stack || err.message || err),
|
||||||
body: req.body, headers: req.header,
|
body: req.body, headers: req.header,
|
||||||
auth: req.headers['x-api-user'],
|
auth: req.headers['x-api-user'],
|
||||||
originalUrl: req.originalUrl
|
originalUrl: req.originalUrl
|
||||||
});
|
});*/
|
||||||
var message = err.message ? err.message : err;
|
var message = err.message ? err.message : err;
|
||||||
message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length);
|
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);
|
res.json(500,{err:message}); //res.end(err.message);
|
||||||
|
|||||||
@@ -297,7 +297,20 @@ var UserSchema = new Schema({
|
|||||||
advancedCollapsed: {type: Boolean, 'default': false},
|
advancedCollapsed: {type: Boolean, 'default': false},
|
||||||
toolbarCollapsed: {type:Boolean, 'default':false},
|
toolbarCollapsed: {type:Boolean, 'default':false},
|
||||||
background: String,
|
background: String,
|
||||||
webhooks: {type: Schema.Types.Mixed, 'default': {}}
|
webhooks: {type: Schema.Types.Mixed, 'default': {}},
|
||||||
|
// For this fields make sure to use strict comparison when searching for falsey values (=== false)
|
||||||
|
// As users who didn't login after these were introduced may have them undefined/null
|
||||||
|
emailNotifications: {
|
||||||
|
unsubscribeFromAll: {type: Boolean, 'default': false},
|
||||||
|
newPM: {type: Boolean, 'default': true},
|
||||||
|
wonChallenge: {type: Boolean, 'default': true},
|
||||||
|
giftedGems: {type: Boolean, 'default': true},
|
||||||
|
giftedSubscription: {type: Boolean, 'default': true},
|
||||||
|
invitedParty: {type: Boolean, 'default': true},
|
||||||
|
invitedGuild: {type: Boolean, 'default': true},
|
||||||
|
//remindersToLogin: {type: Boolean, 'default': true},
|
||||||
|
importantAnnouncements: {type: Boolean, 'default': true}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
profile: {
|
profile: {
|
||||||
blurb: String,
|
blurb: String,
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ auth.setupPassport(router); //FIXME make this consistent with the others
|
|||||||
router.post('/api/v2/register', i18n.getUserLanguage, auth.registerUser);
|
router.post('/api/v2/register', i18n.getUserLanguage, auth.registerUser);
|
||||||
router.post('/api/v2/user/auth/local', i18n.getUserLanguage, auth.loginLocal);
|
router.post('/api/v2/user/auth/local', i18n.getUserLanguage, auth.loginLocal);
|
||||||
router.post('/api/v2/user/auth/social', i18n.getUserLanguage, auth.loginSocial);
|
router.post('/api/v2/user/auth/social', i18n.getUserLanguage, auth.loginSocial);
|
||||||
|
router.delete('/api/v2/user/auth/social', i18n.getUserLanguage, auth.auth, auth.deleteSocial);
|
||||||
router.post('/api/v2/user/reset-password', i18n.getUserLanguage, auth.resetPassword);
|
router.post('/api/v2/user/reset-password', i18n.getUserLanguage, auth.resetPassword);
|
||||||
router.post('/api/v2/user/change-password', i18n.getUserLanguage, auth.auth, auth.changePassword);
|
router.post('/api/v2/user/change-password', i18n.getUserLanguage, auth.auth, auth.changePassword);
|
||||||
router.post('/api/v2/user/change-username', i18n.getUserLanguage, auth.auth, auth.changeUsername);
|
router.post('/api/v2/user/change-username', i18n.getUserLanguage, auth.auth, auth.changeUsername);
|
||||||
|
router.post('/api/v2/user/change-email', i18n.getUserLanguage, auth.auth, auth.changeEmail);
|
||||||
|
|
||||||
router.post('/api/v1/register', i18n.getUserLanguage, auth.registerUser);
|
router.post('/api/v1/register', i18n.getUserLanguage, auth.registerUser);
|
||||||
router.post('/api/v1/user/auth/local', i18n.getUserLanguage, auth.loginLocal);
|
router.post('/api/v1/user/auth/local', i18n.getUserLanguage, auth.loginLocal);
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ var crypto = require('crypto');
|
|||||||
var path = require("path");
|
var path = require("path");
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
|
|
||||||
|
// Set when utils.setupConfig is run
|
||||||
|
var isProd, baseUrl;
|
||||||
|
|
||||||
module.exports.ga = undefined; // set Google Analytics on nconf init
|
module.exports.ga = undefined; // set Google Analytics on nconf init
|
||||||
|
|
||||||
module.exports.sendEmail = function(mailData) {
|
module.exports.sendEmail = function(mailData) {
|
||||||
@@ -22,48 +25,75 @@ module.exports.sendEmail = function(mailData) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMailingInfo(user) {
|
function getUserInfo(user, fields) {
|
||||||
var email, name;
|
var info = {};
|
||||||
if(user.auth.local && user.auth.local.email){
|
|
||||||
email = user.auth.local.email;
|
if(fields.indexOf('name') != -1){
|
||||||
name = user.profile.name || user.auth.local.username;
|
if(user.auth.local){
|
||||||
}else if(user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0] && user.auth.facebook.emails[0].value){
|
info.name = user.profile.name || user.auth.local.username;
|
||||||
email = user.auth.facebook.emails[0].value;
|
}else if(user.auth.facebook){
|
||||||
name = user.auth.facebook.displayName || user.auth.facebook.username;
|
info.name = user.auth.facebook.displayName || user.auth.facebook.username;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {email: email, name: name};
|
|
||||||
|
if(fields.indexOf('email') != -1){
|
||||||
|
if(user.auth.local){
|
||||||
|
info.email = user.auth.local.email;
|
||||||
|
}else if(user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0] && user.auth.facebook.emails[0].value){
|
||||||
|
info.email = user.auth.facebook.emails[0].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fields.indexOf('canSend') != -1){
|
||||||
|
info.canSend = user.preferences.emailNotifications.unsubscribeFromAll !== true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports.getUserInfo = getUserInfo;
|
||||||
|
|
||||||
module.exports.txnEmail = function(mailingInfoArray, emailType, variables){
|
module.exports.txnEmail = function(mailingInfoArray, emailType, variables){
|
||||||
var variables = [{name: 'BASE_URL', content: nconf.get('BASE_URL')}].concat(variables || []);
|
|
||||||
var mailingInfoArray = Array.isArray(mailingInfoArray) ? mailingInfoArray : [mailingInfoArray];
|
var mailingInfoArray = Array.isArray(mailingInfoArray) ? mailingInfoArray : [mailingInfoArray];
|
||||||
|
var variables = [
|
||||||
|
{name: 'BASE_URL', content: baseUrl},
|
||||||
|
{name: 'EMAIL_SETTINGS_URL', content: baseUrl + '/#/options/settings/notifications'}
|
||||||
|
].concat(variables || []);
|
||||||
|
|
||||||
|
// It's important to pass at least a user with its `preferences` as we need to check if he unsubscribed
|
||||||
mailingInfoArray = mailingInfoArray.map(function(mailingInfo){
|
mailingInfoArray = mailingInfoArray.map(function(mailingInfo){
|
||||||
return mailingInfo._id ? getMailingInfo(mailingInfo) : mailingInfo;
|
return mailingInfo._id ? getUserInfo(mailingInfo, ['email', 'name', 'canSend']) : mailingInfo;
|
||||||
}).filter(function(mailingInfo){
|
}).filter(function(mailingInfo){
|
||||||
return mailingInfo.email ? true : false;
|
return (mailingInfo.email && mailingInfo.canSend);
|
||||||
});
|
});
|
||||||
|
|
||||||
request({
|
// When only one recipient send his info as variables
|
||||||
url: nconf.get('EMAIL_SERVER:url') + '/job',
|
if(mailingInfoArray.length === 1 && mailingInfoArray[0].name){
|
||||||
method: 'POST',
|
variables.push({name: 'RECIPIENT_NAME', content: mailingInfoArray[0].name});
|
||||||
auth: {
|
}
|
||||||
user: nconf.get('EMAIL_SERVER:authUser'),
|
|
||||||
pass: nconf.get('EMAIL_SERVER:authPassword')
|
if(isProd && mailingInfoArray.length > 0){
|
||||||
},
|
request({
|
||||||
json: {
|
url: nconf.get('EMAIL_SERVER:url') + '/job',
|
||||||
type: 'email',
|
method: 'POST',
|
||||||
data: {
|
auth: {
|
||||||
emailType: emailType,
|
user: nconf.get('EMAIL_SERVER:authUser'),
|
||||||
to: mailingInfoArray,
|
pass: nconf.get('EMAIL_SERVER:authPassword')
|
||||||
variables: variables
|
|
||||||
},
|
},
|
||||||
options: {
|
json: {
|
||||||
attemps: 5,
|
type: 'email',
|
||||||
backoff: {delay: 10*60*1000, type: 'fixed'}
|
data: {
|
||||||
|
emailType: emailType,
|
||||||
|
to: mailingInfoArray,
|
||||||
|
variables: variables
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
attemps: 5,
|
||||||
|
backoff: {delay: 10*60*1000, type: 'fixed'}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encryption using http://dailyjs.com/2010/12/06/node-tutorial-5/
|
// Encryption using http://dailyjs.com/2010/12/06/node-tutorial-5/
|
||||||
@@ -93,6 +123,9 @@ module.exports.setupConfig = function(){
|
|||||||
if (nconf.get('NODE_ENV') === 'production')
|
if (nconf.get('NODE_ENV') === 'production')
|
||||||
require('newrelic');
|
require('newrelic');
|
||||||
|
|
||||||
|
isProd = nconf.get('NODE_ENV') === 'production';
|
||||||
|
baseUrl = nconf.get('BASE_URL');
|
||||||
|
|
||||||
module.exports.ga = require('universal-analytics')(nconf.get('GA_ID'));
|
module.exports.ga = require('universal-analytics')(nconf.get('GA_ID'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ script(type='text/ng-template', id='partials/options.inventory.seasonalshop.html
|
|||||||
.container-fluid
|
.container-fluid
|
||||||
.stable.row
|
.stable.row
|
||||||
.col-md-2
|
.col-md-2
|
||||||
.seasonalshop_winter2015
|
.seasonalshop_closed
|
||||||
.col-md-10
|
.col-md-10
|
||||||
.popover.static-popover.fade.right.in
|
.popover.static-popover.fade.right.in
|
||||||
.arrow
|
.arrow
|
||||||
h3.popover-title!=env.t('seasonalShopTitle', {linkStart:"<a href='http://blog.habitrpg.com/who' target='_blank'>", linkEnd: "</a>"})
|
h3.popover-title!=env.t('seasonalShopClosedTitle', {linkStart:"<a href='http://blog.habitrpg.com/who' target='_blank'>", linkEnd: "</a>"})
|
||||||
.popover-content
|
.popover-content
|
||||||
p!=env.t('seasonalShopText')
|
p!=env.t('seasonalShopClosedText', {linkStart:"<a href='http://habitrpg.wikia.com/wiki/Grand_Galas' target='_blank'>", linkEnd: "</a>"})
|
||||||
br
|
// br
|
||||||
.well(ng-if='User.user.achievements.rebirths > 0')=env.t('seasonalShopRebirth')
|
.well(ng-if='User.user.achievements.rebirths > 0')=env.t('seasonalShopRebirth')
|
||||||
li.customize-menu.inventory-gear
|
li.customize-menu.inventory-gear
|
||||||
menu.pets-menu(label='{{::label}}', ng-repeat='(set,label) in ::{candycane:env.t("candycaneSet"), ski:env.t("skiSet"), snowflake:env.t("snowflakeSet"), yeti:env.t("yetiSet")}')
|
menu.pets-menu(label='{{::label}}', ng-repeat='(set,label) in ::{candycane:env.t("candycaneSet"), ski:env.t("skiSet"), snowflake:env.t("snowflakeSet"), yeti:env.t("yetiSet")}')
|
||||||
|
|||||||
@@ -63,7 +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}")')
|
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', ['rainbow','yellow','green','purple','blue','TRUred'], 'rainbowColors')
|
||||||
+buyPref('hair.color', ['candycorn','ghostwhite','halloween','midnight','pumpkin','zombie'], 'hauntedColors', 'disabled')
|
+buyPref('hair.color', ['candycorn','ghostwhite','halloween','midnight','pumpkin','zombie'], 'hauntedColors', 'disabled')
|
||||||
+buyPref('hair.color', ['aurora','festive','hollygreen','peppermint','snowy','winterstar'], 'winteryColors')
|
+buyPref('hair.color', ['aurora','festive','hollygreen','peppermint','snowy','winterstar'], 'winteryColors', 'disabled')
|
||||||
|
|
||||||
li.customize-menu
|
li.customize-menu
|
||||||
menu(label=env.t('bodyHair'))
|
menu(label=env.t('bodyHair'))
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ script(id='partials/options.settings.html', type="text/ng-template")
|
|||||||
=env.t('coupon')
|
=env.t('coupon')
|
||||||
li(ng-class="{ active: $state.includes('options.settings.subscription') }")
|
li(ng-class="{ active: $state.includes('options.settings.subscription') }")
|
||||||
a(ui-sref='options.settings.subscription')=env.t('subscription')
|
a(ui-sref='options.settings.subscription')=env.t('subscription')
|
||||||
|
li(ng-class="{ active: $state.includes('options.settings.notifications') }")
|
||||||
|
a(ui-sref='options.settings.notifications')=env.t('notifications')
|
||||||
|
|
||||||
.tab-content
|
.tab-content
|
||||||
.tab-pane.active
|
.tab-pane.active
|
||||||
@@ -84,11 +86,29 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
|
|||||||
|
|
|
|
||||||
=env.t('subWarning3')
|
=env.t('subWarning3')
|
||||||
|
|
||||||
|
.personal-options.col-md-6
|
||||||
.panel.panel-default
|
.panel.panel-default
|
||||||
.panel-heading
|
.panel-heading
|
||||||
span Registration
|
span Registration
|
||||||
.panel-body
|
.panel-body
|
||||||
p(ng-if='user.auth.facebook.id')=env.t('registeredWithFb')
|
div(ng-if='user.auth.facebook.id')
|
||||||
|
button.btn.btn-primary(disabled='disabled', ng-if='!user.auth.local.username')=env.t('registeredWithFb')
|
||||||
|
button.btn.btn-danger(ng-click='http("delete","/api/v2/user/auth/social",null,"detachedFacebook")', ng-if='user.auth.local.username')=env.t('detachFacebook')
|
||||||
|
hr
|
||||||
|
div(ng-if='!user.auth.local.username')
|
||||||
|
p Add local authentication:
|
||||||
|
form(ng-submit='http("post","/api/v2/register",localAuth,"addedLocalAuth")', ng-init='localAuth={}', name='localAuth', novalidate)
|
||||||
|
//-.alert.alert-danger(ng-messages='changeUsername.$error && changeUsername.submitted')=env.t('fillAll')
|
||||||
|
.form-group
|
||||||
|
input.form-control(type='text', placeholder=env.t('username'), ng-model='localAuth.username', required)
|
||||||
|
.form-group
|
||||||
|
input.form-control(type='text', placeholder=env.t('email'), ng-model='localAuth.email', required)
|
||||||
|
.form-group
|
||||||
|
input.form-control(type='password', placeholder=env.t('password'), ng-model='localAuth.password', required)
|
||||||
|
.form-group
|
||||||
|
input.form-control(type='password', placeholder=env.t('confirmPass'), ng-model='localAuth.confirmPassword', required)
|
||||||
|
input.btn.btn-default(type='submit', ng-disabled='localAuth.$invalid', value=env.t('submit'))
|
||||||
|
|
||||||
div(ng-if='user.auth.local.username')
|
div(ng-if='user.auth.local.username')
|
||||||
p=env.t('username')
|
p=env.t('username')
|
||||||
|: {{user.auth.local.username}}
|
|: {{user.auth.local.username}}
|
||||||
@@ -100,31 +120,35 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
|
|||||||
|
|
|
|
||||||
=env.t('loginNameDescription3')
|
=env.t('loginNameDescription3')
|
||||||
p=env.t('email')
|
p=env.t('email')
|
||||||
|: {{::user.auth.local.email}}
|
|: {{user.auth.local.email}}
|
||||||
p
|
|
||||||
small.muted
|
|
||||||
=env.t('emailChange1')
|
|
||||||
|
|
|
||||||
a(href='mailto:admin@habitrpg.com')=env.t('emailChange2')
|
|
||||||
|
|
|
||||||
=env.t('emailChange3')
|
|
||||||
hr
|
hr
|
||||||
|
|
||||||
h5=env.t('changeUsername')
|
h5=env.t('changeUsername')
|
||||||
form(ng-submit='changeUsername(changeUser)', ng-show='user.auth.local')
|
form(ng-submit='changeUser("username", usernameUpdates)', ng-init='usernameUpdates={}', ng-show='user.auth.local', name='changeUsername', novalidate)
|
||||||
|
//-.alert.alert-danger(ng-messages='changeUsername.$error && changeUsername.submitted')=env.t('fillAll')
|
||||||
.form-group
|
.form-group
|
||||||
input.form-control(type='text', placeholder=env.t('newUsername'), ng-model='changeUser.newUsername', required)
|
input.form-control(type='text', placeholder=env.t('newUsername'), ng-model='usernameUpdates.username', required)
|
||||||
.form-group
|
.form-group
|
||||||
input.form-control(type='password', placeholder=env.t('password'), ng-model='changeUser.password', required)
|
input.form-control(type='password', placeholder=env.t('password'), ng-model='usernameUpdates.password', required)
|
||||||
input.btn.btn-default(type='submit', value=env.t('submit'))
|
input.btn.btn-default(type='submit', ng-disabled='changeUsername.$invalid', value=env.t('submit'))
|
||||||
|
|
||||||
|
h5=env.t('changeEmail')
|
||||||
|
form(ng-submit='changeUser("email", emailUpdates)', ng-show='user.auth.local', name='changeEmail', novalidate)
|
||||||
|
.form-group
|
||||||
|
input.form-control(type='text', placeholder=env.t('newEmail'), ng-model='emailUpdates.email', required)
|
||||||
|
.form-group
|
||||||
|
input.form-control(type='password', placeholder=env.t('password'), ng-model='emailUpdates.password', required)
|
||||||
|
input.btn.btn-default(type='submit', ng-disabled='changeEmail.$invalid', value=env.t('submit'))
|
||||||
|
|
||||||
h5=env.t('changePass')
|
h5=env.t('changePass')
|
||||||
form(ng-submit='changePassword(changePass)', ng-show='user.auth.local')
|
form(ng-submit='changeUser("password", passwordUpdates)', ng-show='user.auth.local', name='changePassword', novalidate)
|
||||||
.form-group
|
.form-group
|
||||||
input.form-control(type='password', placeholder=env.t('oldPass'), ng-model='changePass.oldPassword', required)
|
input.form-control(type='password', placeholder=env.t('oldPass'), ng-model='passwordUpdates.oldPassword', required)
|
||||||
.form-group
|
.form-group
|
||||||
input.form-control(type='password', placeholder=env.t('newPass'), ng-model='changePass.newPassword', required)
|
input.form-control(type='password', placeholder=env.t('newPass'), ng-model='passwordUpdates.newPassword', required)
|
||||||
.form-group
|
.form-group
|
||||||
input.form-control(type='password', placeholder=env.t('confirmPass'), ng-model='changePass.confirmNewPassword', required)
|
input.form-control(type='password', placeholder=env.t('confirmPass'), ng-model='passwordUpdates.confirmNewPassword', required)
|
||||||
input.btn.btn-default(type='submit', value=env.t('submit'))
|
input.btn.btn-default(type='submit', ng-disabled='changePassword.$invalid', value=env.t('submit'))
|
||||||
|
|
||||||
|
|
||||||
.panel.panel-default
|
.panel.panel-default
|
||||||
@@ -242,6 +266,64 @@ script(id='partials/feature-matrix-check.html',type='text/ng-template')
|
|||||||
input.focusable(type='checkbox', checked)
|
input.focusable(type='checkbox', checked)
|
||||||
label
|
label
|
||||||
|
|
||||||
|
script(id='partials/options.settings.notifications.html', type="text/ng-template")
|
||||||
|
.container-fluid
|
||||||
|
.row
|
||||||
|
.personal-options.col-md-6
|
||||||
|
.panel.panel-default
|
||||||
|
.panel-heading
|
||||||
|
=env.t('emailNotifications')
|
||||||
|
.panel-body
|
||||||
|
|
||||||
|
.checkbox
|
||||||
|
label
|
||||||
|
input(type='checkbox', ng-disabled='user.preferences.emailNotifications.unsubscribeFromAll === true', ng-model='user.preferences.emailNotifications.newPM', ng-change='set({"preferences.emailNotifications.newPM": user.preferences.emailNotifications.newPM ? true: false})')
|
||||||
|
span=env.t('newPM')
|
||||||
|
|
||||||
|
.checkbox
|
||||||
|
label
|
||||||
|
input(type='checkbox', ng-disabled='user.preferences.emailNotifications.unsubscribeFromAll === true', ng-model='user.preferences.emailNotifications.wonChallenge', ng-change='set({"preferences.emailNotifications.wonChallenge": user.preferences.emailNotifications.wonChallenge ? true: false})')
|
||||||
|
span=env.t('wonChallenge')
|
||||||
|
|
||||||
|
.checkbox
|
||||||
|
label
|
||||||
|
input(type='checkbox', ng-disabled='user.preferences.emailNotifications.unsubscribeFromAll === true', ng-model='user.preferences.emailNotifications.giftedGems', ng-change='set({"preferences.emailNotifications.giftedGems": user.preferences.emailNotifications.giftedGems ? true: false})')
|
||||||
|
span=env.t('giftedGems')
|
||||||
|
|
||||||
|
.checkbox
|
||||||
|
label
|
||||||
|
input(type='checkbox', ng-disabled='user.preferences.emailNotifications.unsubscribeFromAll === true', ng-model='user.preferences.emailNotifications.giftedSubscription', ng-change='set({"preferences.emailNotifications.giftedSubscription": user.preferences.emailNotifications.giftedSubscription ? true: false})')
|
||||||
|
span=env.t('giftedSubscription')
|
||||||
|
|
||||||
|
.checkbox
|
||||||
|
label
|
||||||
|
input(type='checkbox', ng-disabled='user.preferences.emailNotifications.unsubscribeFromAll === true', ng-model='user.preferences.emailNotifications.invitedParty', ng-change='set({"preferences.emailNotifications.invitedParty": user.preferences.emailNotifications.invitedParty ? true: false})')
|
||||||
|
span=env.t('invitedParty')
|
||||||
|
|
||||||
|
.checkbox
|
||||||
|
label
|
||||||
|
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.remindersToLogin', ng-change='set({"preferences.emailNotifications.remindersToLogin": user.preferences.emailNotifications.remindersToLogin ? true: false})')
|
||||||
|
span=env.t('remindersToLogin')
|
||||||
|
|
||||||
|
.checkbox
|
||||||
|
label
|
||||||
|
input(type='checkbox', ng-disabled='user.preferences.emailNotifications.unsubscribeFromAll === true', ng-model='user.preferences.emailNotifications.importantAnnouncements', ng-change='set({"preferences.emailNotifications.importantAnnouncements": user.preferences.emailNotifications.importantAnnouncements ? true: false})')
|
||||||
|
span=env.t('importantAnnouncements')
|
||||||
|
|
||||||
|
hr
|
||||||
|
|
||||||
|
.checkbox
|
||||||
|
label
|
||||||
|
input(type='checkbox', ng-model='user.preferences.emailNotifications.unsubscribeFromAll', ng-change='set({"preferences.emailNotifications.unsubscribeFromAll": user.preferences.emailNotifications.unsubscribeFromAll ? true: false})')
|
||||||
|
span=env.t('unsubscribeAllEmails')
|
||||||
|
|
||||||
|
small=env.t('unsubscribeAllEmailsText')
|
||||||
|
|
||||||
script(id='partials/options.settings.subscription.html',type='text/ng-template')
|
script(id='partials/options.settings.subscription.html',type='text/ng-template')
|
||||||
//-h2=env.t('individualSub')
|
//-h2=env.t('individualSub')
|
||||||
.container-fluid(ng-init='_subscription={key:"basic_earned"}')
|
.container-fluid(ng-init='_subscription={key:"basic_earned"}')
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ script(type='text/ng-template', id='partials/options.social.challenges.detail.me
|
|||||||
button.close(type='button', ng-click='$state.go("^")', aria-hidden='true') ×
|
button.close(type='button', ng-click='$state.go("^")', aria-hidden='true') ×
|
||||||
h3 {{obj.profile.name}}
|
h3 {{obj.profile.name}}
|
||||||
.modal-body
|
.modal-body
|
||||||
habitrpg-tasks(main=false)
|
habitrpg-tasks(main=false, modal='true')
|
||||||
.modal-footer
|
.modal-footer
|
||||||
a.btn.btn-default(ng-click='$state.go("^")')=env.t('close')
|
a.btn.btn-default(ng-click='$state.go("^")')=env.t('close')
|
||||||
|
|
||||||
|
|||||||
@@ -242,6 +242,8 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}')
|
|||||||
a(ui-sref='options.settings.coupon') Coupon
|
a(ui-sref='options.settings.coupon') Coupon
|
||||||
li
|
li
|
||||||
a(ui-sref='options.settings.subscription')=env.t('subscription')
|
a(ui-sref='options.settings.subscription')=env.t('subscription')
|
||||||
|
li
|
||||||
|
a(ui-sref='options.settings.notifications')=env.t('notifications')
|
||||||
ul.toolbar-submenu(ng-click='expandMenu(null)')
|
ul.toolbar-submenu(ng-click='expandMenu(null)')
|
||||||
li
|
li
|
||||||
a(href="http://habitrpg.wikia.com/wiki/FAQ", target='_blank')=env.t('FAQ')
|
a(href="http://habitrpg.wikia.com/wiki/FAQ", target='_blank')=env.t('FAQ')
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ script(type='text/ng-template', id='modals/member.html')
|
|||||||
include ../profiles/achievements
|
include ../profiles/achievements
|
||||||
.modal-footer
|
.modal-footer
|
||||||
.btn-group.pull-left(ng-if='::user')
|
.btn-group.pull-left(ng-if='::user')
|
||||||
button.btn.btn-md.btn-default(ng-show='user.inbox.blocks | contains:profile._id', tooltip=env.t('unblock'), ng-click="user.ops.blockUser({params:{uuid:profile._id}})", tooltip-placement='right')
|
button.btn.btn-md.btn-default(ng-if='user.inbox.blocks | contains:profile._id', tooltip=env.t('unblock'), ng-click="user.ops.blockUser({params:{uuid:profile._id}})", tooltip-placement='right')
|
||||||
span.glyphicon.glyphicon-plus
|
span.glyphicon.glyphicon-plus
|
||||||
button.btn.btn-md.btn-default(ng-hide='profile._id == user._id || user.inbox.blocks | contains:profile._id', tooltip=env.t('block'), ng-click="user.ops.blockUser({params:{uuid:profile._id}})", tooltip-placement='right')
|
button.btn.btn-md.btn-default(ng-if='profile._id != user._id && !profile.contributor.admin && !(user.inbox.blocks | contains:profile._id)', tooltip=env.t('block'), ng-click="user.ops.blockUser({params:{uuid:profile._id}})", tooltip-placement='right')
|
||||||
span.glyphicon.glyphicon-ban-circle
|
span.glyphicon.glyphicon-ban-circle
|
||||||
button.btn.btn-md.btn-default(tooltip=env.t('sendPM'), ng-click="openModal('private-message',{controller:'MemberModalCtrl'})", tooltip-placement='right')
|
button.btn.btn-md.btn-default(tooltip=env.t('sendPM'), ng-click="openModal('private-message',{controller:'MemberModalCtrl'})", tooltip-placement='right')
|
||||||
span.glyphicon.glyphicon-envelope
|
span.glyphicon.glyphicon-envelope
|
||||||
|
|||||||
@@ -1,31 +1,54 @@
|
|||||||
h5 1/30/2015 - HABITRPG BIRTHDAY BASH AND PARTY ROBES! PLUS, LAST CHANCE FOR STARRY KNIGHT ITEM SET, AND WINTER WONDERLAND OUTFITS AND HAIR COLORS!
|
h5 2/3/2015
|
||||||
hr
|
hr
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
.npc_alex.pull-left
|
h5 FEBRUARY BACKGROUNDS REVEALED
|
||||||
h5 HabitRPG Birthday Bash
|
.background_distant_castle.pull-right
|
||||||
p January 31st is HabitRPG's Birthday! All of the NPCs are celebrating, and we've awarded you a bunch of cake for your pets and mounts!
|
p There are three new avatar backgrounds in the <a href='https://habitrpg.com/#/options/profile/backgrounds' target='_blank'>Background Shop</a>! Now your avatar can survey a Distant Castle, toil in the Blacksmithy, or explore a Crystal Cave!
|
||||||
tr
|
p.small.muted by Holseties, Hanztan, and Twitching
|
||||||
td
|
|
||||||
h5 Party Robes
|
|
||||||
.shop_armor_special_birthday.pull-right
|
|
||||||
.shop_armor_special_birthday2015.pull-right
|
|
||||||
p Until February 1st only, there are Party Robes available for free in the Rewards store! If this is your first Birthday bash with us, you can find some Absurd Party Robes; if you already got some last year, then you will find the Silly Party Robes.
|
|
||||||
tr
|
|
||||||
td
|
|
||||||
.promo_mystery_201501.pull-left
|
|
||||||
h5 Last Chance for Starry Knight Item Set
|
|
||||||
p Reminder: this is the final day to <a href='https://habitrpg.com/#/options/settings/subscription' target='_blank'>subscribe</a> and receive the Starry Knight Item Set! If you want the Starry Helm or the Starry Armor, now's the time! Thanks so much for your support <3
|
|
||||||
tr
|
|
||||||
td
|
|
||||||
h5 Last Chance for Winter Wonderland Outfits + Hair Colors
|
|
||||||
.promo_winterclasses2015.pull-right
|
|
||||||
p Tomorrow everything will be back to normal in Habitica, so if you still have any remaining Winter Wonderland Items that you want to buy, you'd better do it now! The <a href='https://habitrpg.com/#/options/inventory/seasonalshop' target='_blank'>Seasonal Edition items</a> and <a href='https://habitrpg.com/#/options/profile/avatar' target='_blank'>Hair Colors</a> won't be back until next December, and if the Limited Edition items return they will have increased prices or changed art, so strike while the iron is hot!
|
|
||||||
|
|
||||||
hr
|
hr
|
||||||
a(href='/static/old-news', target='_blank') Read older news
|
a(href='/static/old-news', target='_blank') Read older news
|
||||||
|
|
||||||
mixin oldNews
|
mixin oldNews
|
||||||
|
h5 2/2/2015
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 February Mystery Box
|
||||||
|
.inventory_present.pull-right
|
||||||
|
p Ooh... What could it be? All Habiticans who are subscribed during the month of February will receive the February Mystery Item Set! It will be revealed on the 24th, so keep your eyes peeled. Thanks for supporting the site <3
|
||||||
|
p.small.muted by Lemoness
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 New Quest Descriptions
|
||||||
|
p We've updated quest descriptions so that when you hover over them, you can now see the Boss or Collection stats and the Rewards that you will gain when you complete the quest!
|
||||||
|
p.small.muted by Blade
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Spread the Word Challenge Has Ended
|
||||||
|
p The Spread the Word Challenge has ended! Thank you to all the participants. It will be some time before the winners are announced because we have to go over all the entries ourselves. Thanks for your patience!
|
||||||
|
h5 1/30/2015
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
.npc_alex.pull-left
|
||||||
|
h5 HabitRPG Birthday Bash
|
||||||
|
p January 31st is HabitRPG's Birthday! All of the NPCs are celebrating, and we've awarded you a bunch of cake for your pets and mounts!
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Party Robes
|
||||||
|
.shop_armor_special_birthday.pull-right
|
||||||
|
.shop_armor_special_birthday2015.pull-right
|
||||||
|
p Until February 1st only, there are Party Robes available for free in the Rewards store! If this is your first Birthday bash with us, you can find some Absurd Party Robes; if you already got some last year, then you will find the Silly Party Robes.
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
.promo_mystery_201501.pull-left
|
||||||
|
h5 Last Chance for Starry Knight Item Set
|
||||||
|
p Reminder: this is the final day to <a href='https://habitrpg.com/#/options/settings/subscription' target='_blank'>subscribe</a> and receive the Starry Knight Item Set! If you want the Starry Helm or the Starry Armor, now's the time! Thanks so much for your support <3
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Last Chance for Winter Wonderland Outfits + Hair Colors
|
||||||
|
.promo_winterclasses2015.pull-right
|
||||||
|
p Tomorrow everything will be back to normal in Habitica, so if you still have any remaining Winter Wonderland Items that you want to buy, you'd better do it now! The <a href='https://habitrpg.com/#/options/inventory/seasonalshop' target='_blank'>Seasonal Edition items</a> and <a href='https://habitrpg.com/#/options/profile/avatar' target='_blank'>Hair Colors</a> won't be back until next December, and if the Limited Edition items return they will have increased prices or changed art, so strike while the iron is hot!
|
||||||
h5 1/26/2015
|
h5 1/26/2015
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
li(bindonce='list', bo-id='"task-"+task.id', ng-repeat='task in obj[list.type+"s"]', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)', ng-class='{"cast-target":spell && (list.type != "reward")}', popover-trigger='mouseenter', data-popover-html="{{task.notes | markdown}}", popover-placement="top", popover-append-to-body='true', ng-show='shouldShow(task, list, user.preferences)')
|
li(bindonce='list', bo-id='"task-"+task.id', ng-repeat='task in obj[list.type+"s"]', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)', ng-class='{"cast-target":spell && (list.type != "reward")}', popover-trigger='mouseenter', data-popover-html="{{task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}', ng-show='shouldShow(task, list, user.preferences)')
|
||||||
// right-hand side control buttons
|
// right-hand side control buttons
|
||||||
.task-meta-controls
|
.task-meta-controls
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user