Merge branch 'develop' into party-chat-translations

This commit is contained in:
Mateus Etto
2018-08-29 20:07:19 +09:00
673 changed files with 25047 additions and 23689 deletions

View File

@@ -329,7 +329,7 @@ api.leaveChallenge = {
};
/**
* @api {get} /api/v3/challenges/user Get challenges for a user.
* @api {get} /api/v3/challenges/user Get challenges for a user
* @apiName GetUserChallenges
* @apiGroup Challenge
* @apiDescription Get challenges the user has access to. Includes public challenges, challenges belonging to the user's group, and challenges the user has already joined.
@@ -640,7 +640,7 @@ api.exportChallengeCsv = {
};
/**
* @api {put} /api/v3/challenges/:challengeId Update the name, description, or leader of a challenge.
* @api {put} /api/v3/challenges/:challengeId Update the name, description, or leader of a challenge
*
* @apiName UpdateChallenge
* @apiGroup Challenge

View File

@@ -159,6 +159,7 @@ api.postChat = {
}
if (!group) throw new NotFound(res.t('groupNotFound'));
if (group.privacy !== 'private' && user.flags.chatRevoked) {
throw new NotAuthorized(res.t('chatPrivilegesRevoked'));
}

View File

@@ -590,11 +590,6 @@ api.joinGroup = {
// @TODO: Review the need for this and if still needed, don't base this on memberCount
if (!group.hasNotCancelled() && group.memberCount === 0) group.leader = user._id; // If new user is only member -> set as leader
if (group.hasNotCancelled()) {
await payments.addSubToGroupUser(user, group);
await group.updateGroupPlan();
}
group.memberCount += 1;
let promises = [group.save(), user.save()];
@@ -638,6 +633,11 @@ api.joinGroup = {
promises = await Promise.all(promises);
if (group.hasNotCancelled()) {
await payments.addSubToGroupUser(user, group);
await group.updateGroupPlan();
}
let response = await Group.toJSONCleanChat(promises[0], user);
let leader = await User.findById(response.leader).select(nameFields).exec();
if (leader) {
@@ -790,7 +790,6 @@ api.leaveGroup = {
}
await group.leave(user, req.query.keep, req.body.keepChallenges);
if (group.hasNotCancelled()) await group.updateGroupPlan(true);
_removeMessagesFromMember(user, group._id);
await user.save();
@@ -804,6 +803,7 @@ api.leaveGroup = {
await payments.cancelGroupSubscriptionForUser(user, group);
}
if (group.hasNotCancelled()) await group.updateGroupPlan(true);
res.respond(200, {});
},
};
@@ -892,10 +892,6 @@ api.removeGroupMember = {
if (isInGroup) {
group.memberCount -= 1;
if (group.hasNotCancelled()) {
await group.updateGroupPlan(true);
await payments.cancelGroupSubscriptionForUser(member, group, true);
}
if (group.quest && group.quest.leader === member._id) {
group.quest.key = undefined;
@@ -944,6 +940,12 @@ api.removeGroupMember = {
member.save(),
group.save(),
]);
if (isInGroup && group.hasNotCancelled()) {
await group.updateGroupPlan(true);
await payments.cancelGroupSubscriptionForUser(member, group, true);
}
res.respond(200, {});
},
};
@@ -1213,6 +1215,16 @@ api.inviteToGroup = {
results.push(...emailResults);
}
let analyticsObject = {
uuid: user._id,
hitType: 'event',
category: 'behavior',
groupType: group.type,
headers: req.headers,
};
res.analytics.track('group invite', analyticsObject);
res.respond(200, results);
},
};

View File

@@ -273,6 +273,7 @@ api.updateHero = {
if (updateData.auth && updateData.auth.blocked === false) {
hero.auth.blocked = false;
}
if (updateData.flags && _.isBoolean(updateData.flags.chatRevoked)) hero.flags.chatRevoked = updateData.flags.chatRevoked;
let savedHero = await hero.save();

View File

@@ -16,7 +16,7 @@ function geti18nBrowserScript (language) {
availableLanguages,
language,
strings: translations[langCode],
momentLang: momentLangs[language.momentLangCode],
momentLang: momentLangs[langCode],
})};
})()`;
}

View File

@@ -478,25 +478,25 @@ api.sendPrivateMessage = {
req.checkBody('message', res.t('messageRequired')).notEmpty();
req.checkBody('toUserId', res.t('toUserIDRequired')).notEmpty().isUUID();
let validationErrors = req.validationErrors();
const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
let sender = res.locals.user;
let message = req.body.message;
let receiver = await User.findById(req.body.toUserId).exec();
const sender = res.locals.user;
const message = req.body.message;
const receiver = await User.findById(req.body.toUserId).exec();
if (!receiver) throw new NotFound(res.t('userNotFound'));
let objections = sender.getObjectionsToInteraction('send-private-message', receiver);
const objections = sender.getObjectionsToInteraction('send-private-message', receiver);
if (objections.length > 0 && !sender.isAdmin()) throw new NotAuthorized(res.t(objections[0]));
await sender.sendMessage(receiver, { receiverMsg: message });
const newMessage = await sender.sendMessage(receiver, { receiverMsg: message });
if (receiver.preferences.emailNotifications.newPM !== false) {
sendTxnEmail(receiver, 'new-pm', [
{name: 'SENDER', content: getUserInfo(sender, ['name']).name},
]);
}
if (receiver.preferences.pushNotifications.newPM !== false) {
sendPushNotification(
receiver,
@@ -510,7 +510,7 @@ api.sendPrivateMessage = {
);
}
res.respond(200, {});
res.respond(200, { message: newMessage });
},
};

View File

@@ -3,7 +3,7 @@ import { authWithHeaders } from '../../middlewares/auth';
let api = {};
// @TODO export this const, cannot export it from here because only routes are exported from controllers
const LAST_ANNOUNCEMENT_TITLE = 'HABITICA COMIC-CON MEETUP AND WIKI SPOTLIGHT ON THE POMODORO TECHNIQUE';
const LAST_ANNOUNCEMENT_TITLE = 'AUGUST SUBSCRIBER ITEMS AND WIKI SPOTLIGHT ON CUSTOMIZING THE HABITICA EXPERIENCE';
const worldDmg = { // @TODO
bailey: false,
};
@@ -30,23 +30,24 @@ api.getNews = {
<div class="mr-3 ${baileyClass}"></div>
<div class="media-body">
<h1 class="align-self-center">${res.t('newStuff')}</h1>
<h2>7/19/2018 - ${LAST_ANNOUNCEMENT_TITLE}</h2>
<h2>8/23/2018 - ${LAST_ANNOUNCEMENT_TITLE}</h2>
</div>
</div>
<hr/>
<div class="media align-items-center">
<div class="media-body">
<h3>Habitica at San Diego Comic Con!</h3>
<p>Beffymaroo will be representing Habitica at San Diego Comic Con this year. If youd like to meet her, along with other fellow Habiticans, join us at the Habitica SDCC Meetup! Beffymaroo will be handing out Habitica stickers, promo codes for the Unconventional Armor set, and other exciting special swag (quantities limited!).</p>
<p>You can find the meetup on Saturday, July 21, at the San Diego Bayfront Hilton lobby from 12:00-1:00 PM! Look for the purple Gryphon banner. Cant wait to meet you :)</p>
<h3>August Subscriber Set Revealed!</h3>
<p>Subscriber Items for August have been revealed: the Lava Dragon Item Set! You only have until August 31 to receive the item set when you <a href='/user/settings/subscription' target='_blank'>subscribe</a>. If you're already an active subscriber, reload the site and then head to Inventory > Items to claim your gear!</p>
</div>
<div class="promo_unconventional_armor ml-3 mb-3"></div>
<div class="promo_mystery_201808 ml-3"></div>
</div>
<p>Subscribers also receive the ability to buy Gems for Gold -- the longer you subscribe, the more Gems you can buy per month! There are other perks as well, such as longer access to uncompressed data and a cute Jackalope pet. Best of all, subscriptions let us keep Habitica running. Thank you very much for your support -- it means a lot to us.</p>
<div class="small mb-3">by Beffymaroo</div>
<div class="media align-items-center">
<div class="scene_pomodoro mr-3"></div>
<div class="scene_casting_spells mr-3 mb-3"></div>
<div class="media-body">
<h3>Wiki Spotlight: The Pomodoro Technique</h3>
<p>This month's <a href='https://habitica.wordpress.com/2018/07/18/pomodoro/' target='_blank'>featured Wiki article</a> is about the Pomodoro Technique! We hope that it will help you as you look for new productivity strategies. Be sure to check it out, and let us know what you think by reaching out on <a href='https://twitter.com/habitica' target='_blank'>Twitter</a>, <a href='http://blog.habitrpg.com' target='_blank'>Tumblr</a>, and <a href='https://facebook.com/habitica' target='_blank'>Facebook</a>.</p>
<h3>Blog Post: Creating a Unique Experience</h3>
<p>This month's <a href='https://habitica.wordpress.com/2018/08/22/creating-a-unique-experience/' target='_blank'>featured Wiki article</a> is about using Habitica's features to create a unique experience! We hope that it will help you as you customize Habitica to make the app even more motivating and fun. Be sure to check it out, and let us know what you think by reaching out on <a href='https://twitter.com/habitica' target='_blank'>Twitter</a>, <a href='http://blog.habitrpg.com' target='_blank'>Tumblr</a>, and <a href='https://facebook.com/habitica' target='_blank'>Facebook</a>.</p>
<div class="small mb-3">by shanaqui and the Wiki Wizards</div>
</div>
</div>

View File

@@ -3,6 +3,7 @@ import {
NotAuthorized,
NotFound,
} from '../../libs/errors';
import { model as PushDevice } from '../../models/pushDevice';
let api = {};
@@ -25,17 +26,17 @@ api.addPushDevice = {
userFieldsToExclude: ['inbox'],
})],
async handler (req, res) {
let user = res.locals.user;
const user = res.locals.user;
req.checkBody('regId', res.t('regIdRequired')).notEmpty();
req.checkBody('type', res.t('typeRequired')).notEmpty().isIn(['ios', 'android']);
let validationErrors = req.validationErrors();
const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
let pushDevices = user.pushDevices;
const pushDevices = user.pushDevices;
let item = {
const item = {
regId: req.body.regId,
type: req.body.type,
};
@@ -44,9 +45,14 @@ api.addPushDevice = {
throw new NotAuthorized(res.t('pushDeviceAlreadyAdded'));
}
pushDevices.push(item);
// Concurrency safe update
const pushDevice = (new PushDevice(item)).toJSON(); // Create a mongo doc
await user.update({
$push: { pushDevices: pushDevice },
}).exec();
await user.save();
// Update the response
user.pushDevices.push(pushDevice);
res.respond(200, user.pushDevices, res.t('pushDeviceAdded'));
},
@@ -70,16 +76,18 @@ api.removePushDevice = {
userFieldsToExclude: ['inbox'],
})],
async handler (req, res) {
let user = res.locals.user;
const user = res.locals.user;
req.checkParams('regId', res.t('regIdRequired')).notEmpty();
let validationErrors = req.validationErrors();
const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
let regId = req.params.regId;
let pushDevices = user.pushDevices;
const regId = req.params.regId;
let indexOfPushDevice = pushDevices.findIndex((element) => {
const pushDevices = user.pushDevices;
const indexOfPushDevice = pushDevices.findIndex((element) => {
return element.regId === regId;
});
@@ -87,8 +95,12 @@ api.removePushDevice = {
throw new NotFound(res.t('pushDeviceNotFound'));
}
// Concurrency safe update
const pullQuery = { $pull: { pushDevices: { $elemMatch: { regId } } } };
await user.update(pullQuery).exec();
// Update the response
pushDevices.splice(indexOfPushDevice, 1);
await user.save();
res.respond(200, user.pushDevices, res.t('pushDeviceRemoved'));
},

View File

@@ -175,6 +175,7 @@ api.createUserTasks = {
hitType: 'event',
category: 'behavior',
taskType: task.type,
headers: req.headers,
});
}
@@ -702,6 +703,7 @@ api.scoreTask = {
category: 'behavior',
taskType: task.type,
direction,
headers: req.headers,
});
}
},

View File

@@ -147,7 +147,7 @@ api.getBuyList = {
};
/**
* @api {get} /api/v3/user/in-app-rewards Get the in app items appaearing in the user's reward column
* @api {get} /api/v3/user/in-app-rewards Get the in app items appearing in the user's reward column
* @apiName UserGetInAppRewards
* @apiGroup User
*
@@ -1445,8 +1445,8 @@ api.userSell = {
* @apiParam (Query) {String} path Full path to unlock. See "content" API call for list of items.
*
* @apiParamExample {curl}
* curl -x POST http://habitica.com/api/v3/user/unlock?path=background.midnight_clouds
* curl -x POST http://habitica.com/api/v3/user/unlock?path=hair.color.midnight
* curl -X POST http://habitica.com/api/v3/user/unlock?path=background.midnight_clouds
* curl -X POST http://habitica.com/api/v3/user/unlock?path=hair.color.midnight
*
* @apiSuccess {Object} data.purchased
* @apiSuccess {Object} data.items

View File

@@ -5,24 +5,24 @@ import { authWithHeaders } from '../../../middlewares/auth';
let api = {};
/**
* @api {post} /api/v3/user/allocate Allocate a single attribute point
* @api {post} /api/v3/user/allocate Allocate a single Stat Point (previously called Attribute Point)
* @apiName UserAllocate
* @apiGroup User
*
* @apiParam (Body) {String="str","con","int","per"} stat Query parameter - Default ='str'
* @apiParam (Query) {String="str","con","int","per"} stat The Stat to increase. Default is 'str'
*
* @apiParamExample {json} Example request
* {"stat":"int"}
* @apiParamExample {curl}
* curl -X POST -d "" https://habitica.com/api/v3/user/allocate?stat=int
*
* @apiSuccess {Object} data Returns stats from the user profile
* @apiSuccess {Object} data Returns stats and notifications from the user profile
*
* @apiError {NotAuthorized} NoPoints Not enough attribute points to increment a stat.
* @apiError {NotAuthorized} NoPoints You don't have enough Stat Points.
*
* @apiErrorExample {json}
* {
* "success": false,
* "error": "NotAuthorized",
* "message": "You don't have enough attribute points."
* "message": "You don't have enough Stat Points."
* }
*/
api.allocate = {
@@ -40,7 +40,7 @@ api.allocate = {
};
/**
* @api {post} /api/v3/user/allocate-bulk Allocate multiple attribute points
* @api {post} /api/v3/user/allocate-bulk Allocate multiple Stat Points
* @apiName UserAllocateBulk
* @apiGroup User
*
@@ -49,22 +49,22 @@ api.allocate = {
* @apiParamExample {json} Example request
* {
* stats: {
* 'int': int,
* 'str': int,
* 'con': int,
* 'per': int,
* },
* "int": int,
* "str": str,
* "con": con,
* "per": per
* }
* }
*
* @apiSuccess {Object} data Returns stats from the user profile
* @apiSuccess {Object} data Returns stats and notifications from the user profile
*
* @apiError {NotAuthorized} NoPoints Not enough attribute points to increment a stat.
* @apiError {NotAuthorized} NoPoints You don't have enough Stat Points.
*
* @apiErrorExample {json}
* {
* "success": false,
* "error": "NotAuthorized",
* "message": "You don't have enough attribute points."
* "message": "You don't have enough Stat Points."
* }
*/
api.allocateBulk = {
@@ -82,7 +82,7 @@ api.allocateBulk = {
};
/**
* @api {post} /api/v3/user/allocate-now Allocate all attribute points
* @api {post} /api/v3/user/allocate-now Allocate all Stat Points
* @apiDescription Uses the user's chosen automatic allocation method, or if none, assigns all to STR. Note: will return success, even if there are 0 points to allocate.
* @apiName UserAllocateNow
* @apiGroup User
@@ -119,7 +119,8 @@ api.allocateBulk = {
* "per": 0,
* "str": 0,
* "con": 0
* }
* },
* "notifications": [ .... ],
* }
* }
*