Merge branch 'develop' of github.com:HabitRPG/habitrpg into add-flag-to-chat
Conflicts: views/options/social/chat-message.jade
This commit is contained in:
+17
-18
@@ -148,21 +148,19 @@ api.loginSocial = function(req, res, next) {
|
||||
network = req.body.network;
|
||||
if (network!=='facebook')
|
||||
return res.json(401, {err:"Only Facebook supported currently."});
|
||||
async.waterfall([
|
||||
function(cb){
|
||||
async.auto({
|
||||
profile: function (cb) {
|
||||
passport._strategies[network].userProfile(access_token, cb);
|
||||
},
|
||||
function(profile, cb) {
|
||||
var q = {};q['auth.'+network+'.id'] = profile.id;
|
||||
User.findOne(q, {_id:1, apiToken:1, auth:1}, function(err, user){
|
||||
if (err) return cb(err);
|
||||
cb(null, {user:user, profile:profile});
|
||||
});
|
||||
},
|
||||
function(data, cb){
|
||||
if (data.user) return cb(null, data.user);
|
||||
user: ['profile', function (cb, results) {
|
||||
var q = {};
|
||||
q['auth.' + network + '.id'] = results.profile.id;
|
||||
User.findOne(q, {_id: 1, apiToken: 1, auth: 1}, cb);
|
||||
}],
|
||||
register: ['profile', 'user', function (cb, results) {
|
||||
if (results.user) return cb(null, results.user);
|
||||
// Create new user
|
||||
var prof = data.profile;
|
||||
var prof = results.profile;
|
||||
var user = {
|
||||
preferences: {
|
||||
language: req.language // User language detected from browser, not saved
|
||||
@@ -175,15 +173,16 @@ api.loginSocial = function(req, res, next) {
|
||||
user = new User(user);
|
||||
user.save(cb);
|
||||
|
||||
if(isProd && prof.emails && prof.emails[0] && prof.emails[0].value){
|
||||
utils.txnEmail({name:prof.displayName || prof.username, email:prof.emails[0].value}, 'welcome');
|
||||
if (isProd && prof.emails && prof.emails[0] && prof.emails[0].value) {
|
||||
utils.txnEmail({name: prof.displayName || prof.username, email: prof.emails[0].value}, 'welcome');
|
||||
}
|
||||
ga.event('register', network).send();
|
||||
}
|
||||
], function(err, user){
|
||||
}]
|
||||
}, function(err, results){
|
||||
if (err) return res.json(401, {err: err.toString ? err.toString() : err});
|
||||
if (user.auth.blocked) return res.json(401, accountSuspended(user._id));
|
||||
return res.json(200, {id: user.id, token:user.apiToken});
|
||||
var acct = results.register[0] ? results.register[0] : results.register;
|
||||
if (acct.auth.blocked) return res.json(401, accountSuspended(acct._id));
|
||||
return res.json(200, {id:acct._id, token:acct.apiToken});
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -147,69 +147,63 @@ api.getMember = function(req, res, next) {
|
||||
// CREATE
|
||||
api.create = function(req, res, next){
|
||||
var user = res.locals.user;
|
||||
var group, chal;
|
||||
|
||||
// First, make sure they've selected a legit group, and store it for later
|
||||
var waterfall = [
|
||||
function(cb){
|
||||
Group.findById(req.body.group).exec(cb);
|
||||
async.auto({
|
||||
get_group: function(cb){
|
||||
var q = {_id:req.body.group};
|
||||
if (req.body.group!='habitrpg') q.members = {$in:[user._id]}; // make sure they're a member of the group
|
||||
Group.findOne(q, cb);
|
||||
},
|
||||
function(_group, cb){
|
||||
if (!_group) return cb("Group." + req.body.group + " not found");
|
||||
group = _group;
|
||||
cb(null);
|
||||
}
|
||||
];
|
||||
save_chal: ['get_group', function(cb, results){
|
||||
var group = results.get_group,
|
||||
prize = +req.body.prize;
|
||||
if (!group)
|
||||
return cb({code:404, err:"Group." + req.body.group + " not found"});
|
||||
if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id)
|
||||
return cb({code:401, err: "Only the group leader can create challenges"});
|
||||
// If they're adding a prize, do some validation
|
||||
if (prize < 0)
|
||||
return cb({code:401, err: 'Challenge prize must be >= 0'});
|
||||
if (req.body.group=='habitrpg' && prize < 1)
|
||||
return cb({code:401, err: 'Prize must be at least 1 Gem for public challenges.'});
|
||||
if (prize > 0) {
|
||||
var groupBalance = ((group.balance && group.leader==user._id) ? group.balance : 0);
|
||||
var prizeCost = prize/4; // I really should have stored user.balance as gems rather than dollars... stupid...
|
||||
if (prizeCost > user.balance + groupBalance)
|
||||
return cb("You can't afford this prize. Purchase more gems or lower the prize amount.")
|
||||
|
||||
// If they're adding a prize, do some validation
|
||||
if (+req.body.prize < 0) return res.json(401, {err: 'Challenge prize must be >= 0'});
|
||||
if (req.body.group=='habitrpg' && +req.body.prize < 1) return res.json(401, {err: 'Prize must be at least 1 Gem for public challenges.'});
|
||||
if (+req.body.prize > 0) {
|
||||
waterfall.push(function(cb){
|
||||
var groupBalance = ((group.balance && group.leader==user._id) ? group.balance : 0);
|
||||
var prizeCost = req.body.prize/4; // I really should have stored user.balance as gems rather than dollars... stupid...
|
||||
if (prizeCost > user.balance + groupBalance)
|
||||
return cb("You can't afford this prize. Purchase more gems or lower the prize amount.")
|
||||
|
||||
if (groupBalance >= prizeCost) {
|
||||
// Group pays for all of prize
|
||||
group.balance -= prizeCost;
|
||||
} else if (groupBalance > 0) {
|
||||
// User pays remainder of prize cost after group
|
||||
var remainder = prizeCost - group.balance;
|
||||
group.balance = 0;
|
||||
user.balance -= remainder;
|
||||
} else {
|
||||
// User pays for all of prize
|
||||
user.balance -= prizeCost;
|
||||
}
|
||||
cb(null);
|
||||
});
|
||||
}
|
||||
|
||||
waterfall = waterfall.concat([
|
||||
function(cb) { // if we're dealing with prize above, arguemnts will be `group, numRows, cb` - else `cb`
|
||||
if (groupBalance >= prizeCost) {
|
||||
// Group pays for all of prize
|
||||
group.balance -= prizeCost;
|
||||
} else if (groupBalance > 0) {
|
||||
// User pays remainder of prize cost after group
|
||||
var remainder = prizeCost - group.balance;
|
||||
group.balance = 0;
|
||||
user.balance -= remainder;
|
||||
} else {
|
||||
// User pays for all of prize
|
||||
user.balance -= prizeCost;
|
||||
}
|
||||
}
|
||||
req.body.leader = user._id;
|
||||
req.body.official = user.contributor.admin && req.body.official;
|
||||
var chal = new Challenge(req.body); // FIXME sanitize
|
||||
chal.members.push(user._id);
|
||||
chal.save(cb)
|
||||
},
|
||||
function(_chal, num, cb){
|
||||
chal = _chal;
|
||||
group.challenges.push(chal._id);
|
||||
group.save(cb);
|
||||
},
|
||||
function(_group, num, cb) {
|
||||
chal.save(cb);
|
||||
}],
|
||||
save_group: ['save_chal', function(cb, results){
|
||||
results.get_group.challenges.push(results.save_chal[0]._id);
|
||||
results.get_group.save(cb);
|
||||
}],
|
||||
sync_user: ['save_group', function(cb, results){
|
||||
// Auto-join creator to challenge (see members.push above)
|
||||
chal.syncToUser(user, cb);
|
||||
}
|
||||
]);
|
||||
async.waterfall(waterfall, function(err){
|
||||
if (err) return next(err);
|
||||
res.json(chal);
|
||||
user = group = chal = null;
|
||||
});
|
||||
results.save_chal[0].syncToUser(user, cb);
|
||||
}]
|
||||
}, function(err, results){
|
||||
if (err) return err.code? res.json(err.code, err) : next(err);
|
||||
return res.json(results.save_chal[0]);
|
||||
user = null;
|
||||
})
|
||||
}
|
||||
|
||||
// UPDATE
|
||||
|
||||
@@ -187,7 +187,7 @@ api.update = function(req, res, next) {
|
||||
if(group.leader !== user._id)
|
||||
return res.json(401, {err: "Only the group leader can update the group!"});
|
||||
|
||||
'name description logo logo leaderMessage leader'.split(' ').forEach(function(attr){
|
||||
'name description logo logo leaderMessage leader leaderOnly'.split(' ').forEach(function(attr){
|
||||
group[attr] = req.body[attr];
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
var iap = require('in-app-purchase');
|
||||
var async = require('async');
|
||||
var payments = require('./index');
|
||||
var nconf = require('nconf');
|
||||
|
||||
var inAppPurchase = require('in-app-purchase');
|
||||
inAppPurchase.config({
|
||||
// this is the path to the directory containing iap-sanbox/iap-live files
|
||||
googlePublicKeyPath: nconf.get("IAP_GOOGLE_KEYDIR")
|
||||
});
|
||||
|
||||
// Validation ERROR Codes
|
||||
var INVALID_PAYLOAD = 6778001;
|
||||
var CONNECTION_FAILED = 6778002;
|
||||
var PURCHASE_EXPIRED = 6778003;
|
||||
|
||||
exports.androidVerify = function(req, res, next) {
|
||||
var iapBody = req.body;
|
||||
var user = res.locals.user;
|
||||
|
||||
iap.setup(function (error) {
|
||||
if (error) {
|
||||
var resObj = {
|
||||
ok: false,
|
||||
data: 'IAP Error'
|
||||
};
|
||||
|
||||
console.error('IAP Setup ERROR');
|
||||
console.error(error);
|
||||
|
||||
res.json(resObj);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
google receipt must be provided as an object
|
||||
{
|
||||
"data": "{stringified data object}",
|
||||
"signature": "signature from google"
|
||||
}
|
||||
*/
|
||||
var testObj = {
|
||||
data: iapBody.transaction.receipt,
|
||||
signature: iapBody.transaction.signature
|
||||
};
|
||||
|
||||
// iap is ready
|
||||
iap.validate(iap.GOOGLE, testObj, function (err, googleRes) {
|
||||
if (err) {
|
||||
var resObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: INVALID_PAYLOAD,
|
||||
message: err.toString()
|
||||
}
|
||||
};
|
||||
|
||||
res.json(resObj);
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (iap.isValidated(googleRes)) {
|
||||
var resObj = {
|
||||
ok: true,
|
||||
data: googleRes
|
||||
};
|
||||
|
||||
payments.buyGems({user:user, paymentMethod:'IAP GooglePlay'});
|
||||
|
||||
// yay good!
|
||||
res.json(resObj);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.iosVerify = function(req, res, next) {
|
||||
console.info(req.body);
|
||||
|
||||
var iapBody = req.body;
|
||||
var user = res.locals.user;
|
||||
|
||||
iap.setup(function (error) {
|
||||
if (error) {
|
||||
var resObj = {
|
||||
ok: false,
|
||||
data: 'IAP Error'
|
||||
};
|
||||
|
||||
console.error('IAP Setup ERROR');
|
||||
console.error(error);
|
||||
|
||||
res.json(resObj);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// iap is ready
|
||||
iap.validate(iap.APPLE, iapBody.transaction.receipt, function (err, appleRes) {
|
||||
if (err) {
|
||||
var resObj = {
|
||||
ok: false,
|
||||
data: {
|
||||
code: INVALID_PAYLOAD,
|
||||
message: err.toString()
|
||||
}
|
||||
};
|
||||
|
||||
res.json(resObj);
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (iap.isValidated(appleRes)) {
|
||||
var resObj = {
|
||||
ok: true,
|
||||
data: appleRes
|
||||
};
|
||||
|
||||
payments.buyGems({user:user, paymentMethod:'IAP AppleStore'});
|
||||
|
||||
// yay good!
|
||||
res.json(resObj);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -7,9 +7,9 @@ var moment = require('moment');
|
||||
var isProduction = nconf.get("NODE_ENV") === "production";
|
||||
var stripe = require('./stripe');
|
||||
var paypal = require('./paypal');
|
||||
var User = require('mongoose').model('User');
|
||||
var members = require('../members')
|
||||
var async = require('async');
|
||||
var iap = require('./iap');
|
||||
|
||||
function revealMysteryItems(user) {
|
||||
_.each(shared.content.gear.flat, function(item) {
|
||||
@@ -123,4 +123,7 @@ exports.paypalSubscribeSuccess = paypal.executeBillingAgreement;
|
||||
exports.paypalSubscribeCancel = paypal.cancelSubscription;
|
||||
exports.paypalCheckout = paypal.createPayment;
|
||||
exports.paypalCheckoutSuccess = paypal.executePayment;
|
||||
exports.paypalIPN = paypal.ipn;
|
||||
exports.paypalIPN = paypal.ipn;
|
||||
|
||||
exports.iapAndroidVerify = iap.androidVerify;
|
||||
exports.iapIosVerify = iap.iosVerify;
|
||||
|
||||
+37
-29
@@ -257,37 +257,45 @@ api.update = function(req, res, next) {
|
||||
};
|
||||
|
||||
api.cron = function(req, res, next) {
|
||||
var user = res.locals.user,
|
||||
progress = user.fns.cron(),
|
||||
ranCron = user.isModified(),
|
||||
quest = shared.content.quests[user.party.quest.key];
|
||||
try{
|
||||
var user = res.locals.user,
|
||||
progress = user.fns.cron(),
|
||||
ranCron = user.isModified(),
|
||||
quest = shared.content.quests[user.party.quest.key];
|
||||
|
||||
if (ranCron) res.locals.wasModified = true;
|
||||
if (!ranCron) return next(null,user);
|
||||
Group.tavernBoss(user,progress);
|
||||
if (!quest) return user.save(next);
|
||||
|
||||
if (ranCron) res.locals.wasModified = true;
|
||||
if (!ranCron) return next(null,user);
|
||||
Group.tavernBoss(user,progress);
|
||||
if (!quest) return user.save(next);
|
||||
|
||||
// If user is on a quest, roll for boss & player, or handle collections
|
||||
// FIXME this saves user, runs db updates, loads user. Is there a better way to handle this?
|
||||
async.waterfall([
|
||||
function(cb){
|
||||
user.save(cb); // make sure to save the cron effects
|
||||
},
|
||||
function(saved, count, cb){
|
||||
var type = quest.boss ? 'boss' : 'collect';
|
||||
Group[type+'Quest'](user,progress,cb);
|
||||
},
|
||||
function(){
|
||||
var cb = arguments[arguments.length-1];
|
||||
// User has been updated in boss-grapple, reload
|
||||
User.findById(user._id, cb);
|
||||
}
|
||||
], function(err, saved) {
|
||||
res.locals.user = saved;
|
||||
next(err,saved);
|
||||
user = progress = quest = null;
|
||||
});
|
||||
// If user is on a quest, roll for boss & player, or handle collections
|
||||
// FIXME this saves user, runs db updates, loads user. Is there a better way to handle this?
|
||||
async.waterfall([
|
||||
function(cb){
|
||||
user.save(cb); // make sure to save the cron effects
|
||||
},
|
||||
function(saved, count, cb){
|
||||
var type = quest.boss ? 'boss' : 'collect';
|
||||
Group[type+'Quest'](user,progress,cb);
|
||||
},
|
||||
function(){
|
||||
var cb = arguments[arguments.length-1];
|
||||
// User has been updated in boss-grapple, reload
|
||||
User.findById(user._id, cb);
|
||||
}
|
||||
], function(err, saved) {
|
||||
if(err) logging.loggly({error: "Cron caught", stack: err.stack || err});
|
||||
res.locals.user = saved;
|
||||
next(err,saved);
|
||||
user = progress = quest = null;
|
||||
});
|
||||
}catch(e){
|
||||
logging.loggly({
|
||||
error: "Cron uncaught",
|
||||
stack: e.stack || e
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
+22
-1
@@ -3,7 +3,23 @@ var winston = require('winston');
|
||||
require('winston-mail').Mail;
|
||||
require('winston-newrelic');
|
||||
|
||||
var logger;
|
||||
var logger, loggly;
|
||||
|
||||
if (nconf.get('LOGGLY:enabled')){
|
||||
loggly = require('loggly').createClient({
|
||||
token: nconf.get('LOGGLY:token'),
|
||||
subdomain: nconf.get('LOGGLY:subdomain'),
|
||||
auth: {
|
||||
username: nconf.get('LOGGLY:username'),
|
||||
password: nconf.get('LOGGLY:password')
|
||||
},
|
||||
//
|
||||
// Optional: Tag to send with EVERY log message
|
||||
//
|
||||
tags: [('heroku-'+nconf.get('BASE_URL'))],
|
||||
json: true
|
||||
});
|
||||
}
|
||||
|
||||
if (logger == null) {
|
||||
logger = new (winston.Logger)({});
|
||||
@@ -49,3 +65,8 @@ module.exports.error = function(/* variable args */) {
|
||||
if (logger)
|
||||
logger.error.apply(logger, arguments);
|
||||
};
|
||||
|
||||
module.exports.loggly = function(/* variable args */){
|
||||
if (loggly)
|
||||
loggly.log.apply(loggly, arguments);
|
||||
};
|
||||
|
||||
+10
-8
@@ -75,6 +75,13 @@ module.exports.errorHandler = function(err, req, res, next) {
|
||||
"\n\nbody: " + JSON.stringify(req.body) +
|
||||
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
|
||||
logging.error(stack);
|
||||
logging.loggly({
|
||||
error: "Uncaught error",
|
||||
stack: (err.stack || err.message || err),
|
||||
body: req.body, headers: req.header,
|
||||
auth: (req.headers['x-api-user'] + ' | ' + req.headers['x-api-key']),
|
||||
originalUrl: req.originalUrl
|
||||
});
|
||||
var message = err.message ? err.message : err;
|
||||
message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length);
|
||||
res.json(500,{err:message}); //res.end(err.message);
|
||||
@@ -173,12 +180,9 @@ module.exports.locals = function(req, res, next) {
|
||||
language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
|
||||
|
||||
var tavern = require('./models/group').tavern;
|
||||
res.locals.habitrpg = {
|
||||
NODE_ENV: nconf.get('NODE_ENV'),
|
||||
BASE_URL: nconf.get('BASE_URL'),
|
||||
GA_ID: nconf.get("GA_ID"),
|
||||
var envVars = _.pick(nconf.get(), 'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY'.split(' '));
|
||||
res.locals.habitrpg = _.merge(envVars, {
|
||||
IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
|
||||
STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY'),
|
||||
getManifestFiles: getManifestFiles,
|
||||
getBuildUrl: getBuildUrl,
|
||||
avalaibleLanguages: i18n.avalaibleLanguages,
|
||||
@@ -193,11 +197,9 @@ module.exports.locals = function(req, res, next) {
|
||||
siteVersion: siteVersion,
|
||||
Content: shared.content,
|
||||
mods: require('./models/user').mods,
|
||||
FACEBOOK_KEY: nconf.get('FACEBOOK_KEY'),
|
||||
|
||||
tavern: tavern, // for world boss
|
||||
worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {}
|
||||
};
|
||||
});
|
||||
|
||||
// Put query-string party invitations into session to be handled later
|
||||
try{
|
||||
|
||||
+4
-1
@@ -26,7 +26,10 @@ var GroupSchema = new Schema({
|
||||
# id: String
|
||||
# }]
|
||||
*/
|
||||
|
||||
leaderOnly: { // restrict group actions to leader (members can't do them)
|
||||
challenges: {type:Boolean, 'default':false},
|
||||
//invites: {type:Boolean, 'default':false}
|
||||
},
|
||||
memberCount: {type: Number, 'default': 0},
|
||||
challengeCount: {type: Number, 'default': 0},
|
||||
balance: Number,
|
||||
|
||||
@@ -17,4 +17,7 @@ router.post("/stripe/subscribe/edit", auth.auth, i18n.getUserLanguage, payments.
|
||||
//router.get("/stripe/subscribe", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead
|
||||
router.get("/stripe/subscribe/cancel", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribeCancel);
|
||||
|
||||
router.post("/iap/android/verify", auth.authWithUrl, /*i18n.getUserLanguage, */payments.iapAndroidVerify);
|
||||
router.post("/iap/ios/verify", /*auth.authWithUrl, i18n.getUserLanguage, */ payments.iapIosVerify);
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user