Merge branch 'develop' into iap-fix

This commit is contained in:
Matteo Pagliazzi
2016-07-04 15:45:56 +02:00
311 changed files with 37611 additions and 4751 deletions
-23
View File
@@ -6,7 +6,6 @@ var async = require('async');
var utils = require('../../libs/api-v2/utils');
var nconf = require('nconf');
var request = require('request');
var FirebaseTokenGenerator = require('firebase-token-generator');
import {
model as User,
} from '../../models/user';
@@ -351,28 +350,6 @@ api.changePassword = function(req, res, next) {
})
};
// DISABLED FOR API v2
/*var firebaseTokenGeneratorInstance = new FirebaseTokenGenerator(nconf.get('FIREBASE:SECRET'));
api.getFirebaseToken = function(req, res, next) {
var user = res.locals.user;
// Expires 24 hours after now (60*60*24*1000) (in milliseconds)
var expires = new Date();
expires.setTime(expires.getTime() + 86400000);
var token = firebaseTokenGeneratorInstance
.createToken({
uid: user._id,
isHabiticaUser: true
}, {
expires: expires
});
res.status(200).json({
token: token,
expires: expires
});
};*/
// DISABLED FOR API v2
/*api.setupPassport = function(router) {
@@ -32,7 +32,6 @@ var isProd = nconf.get('NODE_ENV') === 'production';
var api = module.exports;
var pushNotify = require('./pushNotifications');
var analytics = utils.analytics;
var firebase = require('../../libs/api-v2/firebase');
/*
------------------------------------------------------------------------
@@ -233,8 +232,6 @@ api.create = function(req, res, next) {
function(cb){user.save(cb)},
function(saved,ct,cb){group.save(cb)},
function(saved,ct,cb){
firebase.updateGroupData(saved);
firebase.addUserToGroup(saved._id, user._id);
saved.getTransformedData({
populateMembers: nameFields,
cb,
@@ -278,7 +275,6 @@ api.update = function(req, res, next) {
group.save(function(err, saved){
if (err) return next(err);
firebase.updateGroupData(saved);
res.sendStatus(204);
});
}
@@ -548,7 +544,6 @@ api.join = function(req, res, next) {
group.save(cb);
},
function(cb){
firebase.addUserToGroup(group._id, user._id);
group.getTransformedData({
cb,
populateMembers: group.type === 'party' ? partyFields : nameFields,
@@ -33,7 +33,6 @@ import v3UserController from '../api-v3/user';
let i18n = shared.i18n;
var api = module.exports;
var firebase = require('../../libs/api-v2/firebase');
var webhook = require('../../libs/api-v2/webhook');
const partyMembersFields = 'profile.name stats achievements items.special';
@@ -468,7 +467,6 @@ api.delete = function(req, res, next) {
return user.remove();
})
.then(() => {
firebase.deleteUser(user._id);
res.sendStatus(200);
})
.catch(next);
+71 -23
View File
@@ -18,8 +18,8 @@ import { model as Group } from '../../models/group';
import { model as EmailUnsubscription } from '../../models/emailUnsubscription';
import { sendTxn as sendTxnEmail } from '../../libs/api-v3/email';
import { decrypt } from '../../libs/api-v3/encryption';
import FirebaseTokenGenerator from 'firebase-token-generator';
import { send as sendEmail } from '../../libs/api-v3/email';
import pusher from '../../libs/api-v3/pusher';
let api = {};
@@ -282,6 +282,76 @@ api.loginSocial = {
},
};
/*
* @apiIgnore Private route
* @api {post} /api/v3/user/auth/pusher Pusher.com authentication
* @apiDescription Authentication for Pusher.com private and presence channels
* @apiVersion 3.0.0
* @apiName UserAuthPusher
* @apiGroup User
*
* @apiParam {String} socket_id Body parameter
* @apiParam {String} channel_name Body parameter
*
* @apiSuccess {String} auth The authentication token
*/
api.pusherAuth = {
method: 'POST',
middlewares: [authWithHeaders()],
url: '/user/auth/pusher',
async handler (req, res) {
let user = res.locals.user;
req.checkBody('socket_id').notEmpty();
req.checkBody('channel_name').notEmpty();
let validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
let socketId = req.body.socket_id;
let channelName = req.body.channel_name;
// Channel names are in the form of {presence|private}-{group|...}-{resourceId}
let [channelType, resourceType, ...resourceId] = channelName.split('-');
if (['presence'].indexOf(channelType) === -1) { // presence is used only for parties, private for guilds too
throw new BadRequest('Invalid Pusher channel type.');
}
if (resourceType !== 'group') { // only groups are supported
throw new BadRequest('Invalid Pusher resource type.');
}
resourceId = resourceId.join('-'); // the split at the beginning had split resourceId too
if (!validator.isUUID(resourceId)) {
throw new BadRequest('Invalid Pusher resource id, must be a UUID.');
}
// Only the user's party is supported for now
if (user.party._id !== resourceId) {
throw new NotFound('Resource id must be the user\'s party.');
}
let authResult;
// Max 100 members for presence channel - parties only
if (channelType === 'presence') {
let presenceData = {
user_id: user._id, // eslint-disable-line camelcase
// Max 1KB
user_info: {}, // eslint-disable-line camelcase
};
authResult = pusher.authenticate(socketId, channelName, presenceData);
} else {
authResult = pusher.authenticate(socketId, channelName);
}
// Not using res.respond because Pusher requires a different response format
res.status(200).json(authResult);
},
};
/**
* @api {put} /api/v3/user/auth/update-username Update username
* @apiDescription Update the username of a local user
@@ -472,28 +542,6 @@ api.updateEmail = {
},
};
const firebaseTokenGenerator = new FirebaseTokenGenerator(nconf.get('FIREBASE:SECRET'));
// Internal route
api.getFirebaseToken = {
method: 'POST',
url: '/user/auth/firebase',
middlewares: [authWithHeaders()],
async handler (req, res) {
let user = res.locals.user;
// Expires 24 hours from now (60*60*24*1000) (in milliseconds)
let expires = new Date();
expires.setTime(expires.getTime() + 86400000);
let token = firebaseTokenGenerator.createToken({
uid: user._id,
isHabiticaUser: true,
}, { expires });
res.respond(200, {token, expires});
},
};
/**
* @api {delete} /api/v3/user/auth/social/:network Delete social authentication method
* @apiDescription Remove a social authentication method (only facebook supported) from a user profile. The user must have local authentication enabled
+23 -14
View File
@@ -8,6 +8,7 @@ import {
import _ from 'lodash';
import { removeFromArray } from '../../libs/api-v3/collectionManipulators';
import { getUserInfo, getGroupUrl, sendTxn } from '../../libs/api-v3/email';
import pusher from '../../libs/api-v3/pusher';
import nconf from 'nconf';
import Bluebird from 'bluebird';
@@ -49,12 +50,12 @@ api.getChat = {
/**
* @api {post} /api/v3/groups/:groupId/chat Post chat message to a group
* @apiVersion 3.0.0
* @apiName PostCat
* @apiName PostChat
* @apiGroup Chat
*
* @apiParam {UUID} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted)
* @apiParam {message} Body parameter - message The message to post
* @apiParam {previousMsg} previousMsg Query parameter - The previous chat message which will force a return of the full group chat
* @apiParam {string} message Body parameter - message The message to post
* @apiParam {UUID} previousMsg Query parameter - The previous chat message which will force a return of the full group chat
*
* @apiSuccess data An array of chat messages if a new message was posted after previousMsg, otherwise the posted message
*/
@@ -83,7 +84,7 @@ api.postChat = {
let lastClientMsg = req.query.previousMsg;
chatUpdated = lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg ? true : false;
group.sendChat(req.body.message, user);
let newChatMessage = group.sendChat(req.body.message, user);
let toSave = [group.save()];
@@ -93,6 +94,14 @@ api.postChat = {
}
let [savedGroup] = await Bluebird.all(toSave);
// real-time chat is only enabled for private groups (for now only for parties)
if (savedGroup.privacy === 'private' && savedGroup.type === 'party') {
// req.body.pusherSocketId is sent from official clients to identify the sender user's real time socket
// see https://pusher.com/docs/server_api_guide/server_excluding_recipients
pusher.trigger(`presence-group-${savedGroup._id}`, 'new-chat', newChatMessage, req.body.pusherSocketId);
}
if (chatUpdated) {
res.respond(200, {chat: Group.toJSONCleanChat(savedGroup, user).chat});
} else {
@@ -107,8 +116,8 @@ api.postChat = {
* @apiName LikeChat
* @apiGroup Chat
*
* @apiParam {groupId} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted)
* @apiParam {chatId} chatId The chat message _id
* @apiParam {UUID} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted)
* @apiParam {UUID} chatId The chat message _id
*
* @apiSuccess {Object} data The liked chat message
*/
@@ -154,8 +163,8 @@ api.likeChat = {
* @apiName FlagChat
* @apiGroup Chat
*
* @apiParam {groupId} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted)
* @apiParam {chatId} chatId The chat message id
* @apiParam {UUID} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted)
* @apiParam {UUID} chatId The chat message id
*
* @apiSuccess {object} data The flagged chat message
*/
@@ -243,8 +252,8 @@ api.flagChat = {
* @apiName ClearFlags
* @apiGroup Chat
*
* @apiParam {groupId} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted)
* @apiParam {chatId} chatId The chat message id
* @apiParam {UUID} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted)
* @apiParam {UUID} chatId The chat message id
*
* @apiSuccess {Object} data An empty object
*/
@@ -313,12 +322,12 @@ api.clearChatFlags = {
};
/**
* @api {post} /api/v3/groups/:groupId/chat/:chatId/seen Seen a group chat message
* @api {post} /api/v3/groups/:groupId/chat/seen Mark all messages as read for a group
* @apiVersion 3.0.0
* @apiName SeenChat
* @apiGroup Chat
*
* @apiParam {groupId} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted)
* @apiParam {UUID} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted)
*
* @apiSuccess {Object} data An empty object
*/
@@ -354,8 +363,8 @@ api.seenChat = {
* @apiGroup Chat
*
* @apiParam {string} previousMsg Query parameter - The last message fetched by the client so that the whole chat will be returned only if new messages have been posted in the meantime
* @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted)
* @apiParam {string} chatId The chat message id
* @apiParam {UUID} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted)
* @apiParam {UUID} chatId The chat message id
*
* @apiSuccess data The updated chat array or an empty object if no message was posted after previousMsg
* @apiSuccess {Object} data An empty object when the previous message was deleted
+11 -9
View File
@@ -17,10 +17,11 @@ import {
NotAuthorized,
} from '../../libs/api-v3/errors';
import { removeFromArray } from '../../libs/api-v3/collectionManipulators';
import * as firebase from '../../libs/api-v3/firebase';
import { sendTxn as sendTxnEmail } from '../../libs/api-v3/email';
import { encrypt } from '../../libs/api-v3/encryption';
import sendPushNotification from '../../libs/api-v3/pushNotifications';
import pusher from '../../libs/api-v3/pusher';
let api = {};
/**
@@ -66,9 +67,6 @@ api.createGroup = {
profile: {name: user.profile.name},
};
res.respond(201, response); // do not remove chat flags data as we've just created the group
firebase.updateGroupData(savedGroup);
firebase.addUserToGroup(savedGroup._id, user._id);
},
};
@@ -179,8 +177,6 @@ api.updateGroup = {
};
}
res.respond(200, response);
firebase.updateGroupData(savedGroup);
},
};
@@ -278,8 +274,6 @@ api.joinGroup = {
response.leader = leader.toJSON({minimize: true});
}
res.respond(200, response);
firebase.addUserToGroup(group._id, user._id);
},
};
@@ -449,7 +443,15 @@ api.removeGroupMember = {
if (isInGroup === 'guild') {
removeFromArray(member.guilds, group._id);
}
if (isInGroup === 'party') member.party._id = undefined; // TODO remove quest information too? Use group.leave()?
if (isInGroup === 'party') {
// Tell the realtime clients that a user is being removed
// If the user that is being removed is still connected, they'll get disconnected automatically
pusher.trigger(`presence-group-${group._id}`, 'user-removed', {
userId: user._id,
});
member.party._id = undefined; // TODO remove quest information too? Use group.leave()?
}
if (member.newMessages[group._id]) {
member.newMessages[group._id] = undefined;
+2 -1
View File
@@ -76,7 +76,7 @@ api.getHeroes = {
// Note, while the following routes are called getHero / updateHero
// they can be used by admins to get/update any user
const heroAdminFields = 'contributor balance profile.name purchased items auth';
const heroAdminFields = 'contributor balance profile.name purchased items auth flags.chatRevoked';
/**
* @api {get} /api/v3/hall/heroes/:heroId Get any user ("hero") given the UUID
@@ -170,6 +170,7 @@ api.updateHero = {
}
if (updateData.auth && _.isBoolean(updateData.auth.blocked)) hero.auth.blocked = updateData.auth.blocked;
if (updateData.flags && _.isBoolean(updateData.flags.chatRevoked)) hero.flags.chatRevoked = updateData.flags.chatRevoked;
let savedHero = await hero.save();
let heroJSON = savedHero.toJSON();
@@ -13,7 +13,6 @@ import {
import { model as User } from '../../models/user';
import Bluebird from 'bluebird';
import _ from 'lodash';
import * as firebase from '../../libs/api-v3/firebase';
import * as passwordUtils from '../../libs/api-v3/password';
let api = {};
@@ -230,8 +229,6 @@ api.deleteUser = {
await user.remove();
res.respond(200, {});
firebase.deleteUser(user._id);
},
};
@@ -105,10 +105,10 @@ api.checkout = {
CurrencyCode: 'USD',
Amount: amount,
},
SellerNote: 'HabitRPG Payment',
SellerNote: 'Habitica Payment',
SellerOrderAttributes: {
SellerOrderId: shared.uuid(),
StoreName: 'HabitRPG',
StoreName: 'Habitica',
},
},
});
@@ -122,7 +122,7 @@ api.checkout = {
CurrencyCode: 'USD',
Amount: amount,
},
SellerAuthorizationNote: 'HabitRPG Payment',
SellerAuthorizationNote: 'Habitica Payment',
TransactionTimeout: 0,
CaptureNow: true,
});
@@ -177,11 +177,11 @@ api.subscribe = {
await amzLib.setBillingAgreementDetails({
AmazonBillingAgreementId: billingAgreementId,
BillingAgreementAttributes: {
SellerNote: 'HabitRPG Subscription',
SellerNote: 'Habitica Subscription',
SellerBillingAgreementAttributes: {
SellerBillingAgreementId: shared.uuid(),
StoreName: 'HabitRPG',
CustomInformation: 'HabitRPG Subscription',
StoreName: 'Habitica',
CustomInformation: 'Habitica Subscription',
},
},
});
@@ -197,13 +197,13 @@ api.subscribe = {
CurrencyCode: 'USD',
Amount: sub.price,
},
SellerAuthorizationNote: 'HabitRPG Subscription Payment',
SellerAuthorizationNote: 'Habitica Subscription Payment',
TransactionTimeout: 0,
CaptureNow: true,
SellerNote: 'HabitRPG Subscription Payment',
SellerNote: 'Habitica Subscription Payment',
SellerOrderAttributes: {
SellerOrderId: shared.uuid(),
StoreName: 'HabitRPG',
StoreName: 'Habitica',
},
});
@@ -63,14 +63,14 @@ api.checkout = {
req.session.gift = req.query.gift;
let amount = 5.00;
let description = 'HabitRPG gems';
let description = 'Habitica Gems';
if (gift) {
if (gift.type === 'gems') {
amount = Number(gift.gems.amount / 4).toFixed(2);
description = `${description} (Gift)`;
} else {
amount = Number(shared.content.subscriptionBlocks[gift.subscription.key].price).toFixed(2);
description = 'mo. HabitRPG Subscription (Gift)';
description = 'mo. Habitica Subscription (Gift)';
}
}
@@ -166,7 +166,7 @@ api.subscribe = {
if (!coupon) throw new NotAuthorized(res.t('invalidCoupon'));
}
let billingPlanTitle = `HabitRPG Subscription ($${sub.price} every ${sub.months} months, recurring)`;
let billingPlanTitle = `Habitica Subscription ($${sub.price} every ${sub.months} months, recurring)`;
let billingAgreementAttributes = {
name: billingPlanTitle,
description: billingPlanTitle,
@@ -248,7 +248,7 @@ api.subscribeCancel = {
},
};
// General IPN handler. We catch cancelled HabitRPG subscriptions for users who manually cancel their
// General IPN handler. We catch cancelled Habitica subscriptions for users who manually cancel their
// recurring paypal payments in their paypal dashboard. TODO ? Remove this when we can move to webhooks or some other solution
/**