mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
Merged in develop
This commit is contained in:
@@ -251,7 +251,7 @@ api.joinChallenge = {
|
||||
if (challenge.isMember(user)) throw new NotAuthorized(res.t('userAlreadyInChallenge'));
|
||||
|
||||
let group = await Group.getGroup({user, groupId: challenge.group, fields: basicGroupFields, optionalMembership: true});
|
||||
if (!group || !challenge.hasAccess(user, group)) throw new NotFound(res.t('challengeNotFound'));
|
||||
if (!group || !challenge.canJoin(user, group)) throw new NotFound(res.t('challengeNotFound'));
|
||||
|
||||
challenge.memberCount += 1;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,7 +12,6 @@ import { getUserInfo, getGroupUrl, sendTxn } from '../../libs/email';
|
||||
import slack from '../../libs/slack';
|
||||
import pusher from '../../libs/pusher';
|
||||
import { getAuthorEmailFromMessage } from '../../libs/chat';
|
||||
import { userIsMuted, muteUserForLife } from '../../libs/chat/mute';
|
||||
import { chatReporterFactory } from '../../libs/chatReporting/chatReporterFactory';
|
||||
import nconf from 'nconf';
|
||||
import bannedWords from '../../libs/bannedWords';
|
||||
@@ -125,7 +124,6 @@ api.postChat = {
|
||||
if (textContainsBannedSlur(req.body.message)) {
|
||||
let message = req.body.message;
|
||||
user.flags.chatRevoked = true;
|
||||
muteUserForLife(user);
|
||||
await user.save();
|
||||
|
||||
// Email the mods
|
||||
@@ -162,7 +160,7 @@ api.postChat = {
|
||||
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
|
||||
if (group.privacy !== 'private' && userIsMuted(user)) {
|
||||
if (group.privacy !== 'private' && user.flags.chatRevoked) {
|
||||
throw new NotAuthorized(res.t('chatPrivilegesRevoked'));
|
||||
}
|
||||
|
||||
|
||||
@@ -592,11 +592,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()];
|
||||
@@ -640,6 +635,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) {
|
||||
@@ -792,7 +792,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();
|
||||
|
||||
@@ -806,6 +805,7 @@ api.leaveGroup = {
|
||||
await payments.cancelGroupSubscriptionForUser(user, group);
|
||||
}
|
||||
|
||||
if (group.hasNotCancelled()) await group.updateGroupPlan(true);
|
||||
res.respond(200, {});
|
||||
},
|
||||
};
|
||||
@@ -894,10 +894,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;
|
||||
@@ -946,6 +942,12 @@ api.removeGroupMember = {
|
||||
member.save(),
|
||||
group.save(),
|
||||
]);
|
||||
|
||||
if (isInGroup && group.hasNotCancelled()) {
|
||||
await group.updateGroupPlan(true);
|
||||
await payments.cancelGroupSubscriptionForUser(member, group, true);
|
||||
}
|
||||
|
||||
res.respond(200, {});
|
||||
},
|
||||
};
|
||||
@@ -1086,6 +1088,16 @@ api.inviteToGroup = {
|
||||
results.push(...usernameResults);
|
||||
}
|
||||
|
||||
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);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -143,7 +143,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 flags.chatRevoked flags.chatRevokedEndDate';
|
||||
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
|
||||
@@ -275,7 +275,6 @@ api.updateHero = {
|
||||
}
|
||||
|
||||
if (updateData.flags && _.isBoolean(updateData.flags.chatRevoked)) hero.flags.chatRevoked = updateData.flags.chatRevoked;
|
||||
if (updateData.flags && updateData.flags.chatRevokedEndDate) hero.flags.chatRevokedEndDate = updateData.flags.chatRevokedEndDate;
|
||||
|
||||
let savedHero = await hero.save();
|
||||
let heroJSON = savedHero.toJSON();
|
||||
|
||||
29
website/server/controllers/api-v3/inbox.js
Normal file
29
website/server/controllers/api-v3/inbox.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import { toArray, orderBy } from 'lodash';
|
||||
|
||||
let api = {};
|
||||
|
||||
/* NOTE most inbox routes are either in the user or members controller */
|
||||
|
||||
/**
|
||||
* @api {get} /api/v3/inbox/messages Get inbox messages for a user
|
||||
* @apiPrivate
|
||||
* @apiName GetInboxMessages
|
||||
* @apiGroup Inbox
|
||||
* @apiDescription Get inbox messages for a user
|
||||
*
|
||||
* @apiSuccess {Array} data An array of inbox messages
|
||||
*/
|
||||
api.getInboxMessages = {
|
||||
method: 'GET',
|
||||
url: '/inbox/messages',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
const messagesObj = res.locals.user.inbox.messages;
|
||||
const messagesArray = orderBy(toArray(messagesObj), ['timestamp'], ['desc']);
|
||||
|
||||
res.respond(200, messagesArray);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = api;
|
||||
@@ -32,6 +32,62 @@ let api = {};
|
||||
*
|
||||
* @apiSuccess {Object} data The member object
|
||||
*
|
||||
* @apiSuccess (Object) data.inbox Basic information about person's inbox
|
||||
* @apiSuccess (Object) data.stats Includes current stats and buffs
|
||||
* @apiSuccess (Object) data.profile Includes name
|
||||
* @apiSuccess (Object) data.preferences Includes info about appearance and public prefs
|
||||
* @apiSuccess (Object) data.party Includes basic info about current party and quests
|
||||
* @apiSuccess (Object) data.items Basic inventory information includes quests, food, potions, eggs, gear, special items
|
||||
* @apiSuccess (Object) data.achievements Lists current achievements
|
||||
* @apiSuccess (Object) data.auth Includes latest timestamps
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "success": true,
|
||||
* "data": {
|
||||
* "_id": "99999999-9999-9999-9999-8f14c101aeff",
|
||||
* "inbox": {
|
||||
* "optOut": false
|
||||
* },
|
||||
* "stats": {
|
||||
* ---INCLUDES STATS AND BUFFS---
|
||||
* },
|
||||
* "profile": {
|
||||
* "name": "Ezra"
|
||||
* },
|
||||
* "preferences": {
|
||||
* ---INCLUDES INFO ABOUT APPEARANCE AND PUBLIC PREFS---
|
||||
* },
|
||||
* "party": {
|
||||
* "_id": "12345678-0987-abcd-82a6-837c81db4c1e",
|
||||
* "quest": {
|
||||
* "RSVPNeeded": false,
|
||||
* "progress": {}
|
||||
* },
|
||||
* },
|
||||
* "items": {
|
||||
* "lastDrop": {
|
||||
* "count": 0,
|
||||
* "date": "2017-01-15T02:41:35.009Z"
|
||||
* },
|
||||
* ----INCLUDES QUESTS, FOOD, POTIONS, EGGS, GEAR, CARDS, SPECIAL ITEMS (E.G. SNOWBALLS)----
|
||||
* }
|
||||
* },
|
||||
* "achievements": {
|
||||
* "partyUp": true,
|
||||
* "habitBirthdays": 2,
|
||||
* },
|
||||
* "auth": {
|
||||
* "timestamps": {
|
||||
* "loggedin": "2017-03-05T12:30:54.545Z",
|
||||
* "created": "2017-01-12T03:30:11.842Z"
|
||||
* }
|
||||
* },
|
||||
* "id": "99999999-9999-9999-9999-8f14c101aeff"
|
||||
* }
|
||||
* }
|
||||
*)
|
||||
*
|
||||
* @apiUse UserNotFound
|
||||
*/
|
||||
api.getMember = {
|
||||
@@ -302,6 +358,21 @@ function _getMembersForItem (type) {
|
||||
* @apiParam (Query) {Boolean} includeAllPublicFields Query parameter available only when fetching a party. If === `true` then all public fields for members will be returned (like when making a request for a single member)
|
||||
*
|
||||
* @apiSuccess {Array} data An array of members, sorted by _id
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "success": true,
|
||||
* "data": [
|
||||
* {
|
||||
* "_id": "00000001-1111-9999-9000-111111111111",
|
||||
* "profile": {
|
||||
* "name": "Jiminy"
|
||||
* },
|
||||
* "id": "00000001-1111-9999-9000-111111111111"
|
||||
* },
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @apiUse ChallengeNotFound
|
||||
* @apiUse GroupNotFound
|
||||
*/
|
||||
@@ -325,6 +396,21 @@ api.getMembersForGroup = {
|
||||
*
|
||||
* @apiSuccess {array} data An array of invites, sorted by _id
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "success": true,
|
||||
* "data": [
|
||||
* {
|
||||
* "_id": "99f3cb9d-4af8-4ca4-9b82-6b2a6bf59b7a",
|
||||
* "profile": {
|
||||
* "name": "DoomSmoocher"
|
||||
* },
|
||||
* "id": "99f3cb9d-4af8-4ca4-9b82-6b2a6bf59b7a"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @apiUse ChallengeNotFound
|
||||
* @apiUse GroupNotFound
|
||||
*/
|
||||
@@ -375,6 +461,48 @@ api.getMembersForChallenge = {
|
||||
*
|
||||
* @apiSuccess {Object} data Return an object with member _id, profile.name and a tasks object with the challenge tasks for the member
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "data": {
|
||||
* "_id": "b0413351-405f-416f-8787-947ec1c85199",
|
||||
* "profile": {"name": "MadPink"},
|
||||
* "tasks": [
|
||||
* {
|
||||
* "_id": "9cd37426-0604-48c3-a950-894a6e72c156",
|
||||
* "text": "Make sure the place where you sleep is quiet, dark, and cool.",
|
||||
* "updatedAt": "2017-06-17T17:44:15.916Z",
|
||||
* "createdAt": "2017-06-17T17:44:15.916Z",
|
||||
* "reminders": [],
|
||||
* "group": {
|
||||
* "approval": {
|
||||
* "requested": false,
|
||||
* "approved": false,
|
||||
* "required": false
|
||||
* },
|
||||
* "assignedUsers": []
|
||||
* },
|
||||
* "challenge": {
|
||||
* "taskId": "6d3758b1-071b-4bfa-acd6-755147a7b5f6",
|
||||
* "id": "4db6bd82-b829-4bf2-bad2-535c14424a3d",
|
||||
* "shortName": "Take This June 2017"
|
||||
* },
|
||||
* "attribute": "str",
|
||||
* "priority": 1,
|
||||
* "value": 0,
|
||||
* "notes": "",
|
||||
* "type": "todo",
|
||||
* "checklist": [],
|
||||
* "collapseChecklist": false,
|
||||
* "completed": false,
|
||||
* },
|
||||
* "startDate": "2016-09-01T05:00:00.000Z",
|
||||
* "everyX": 1,
|
||||
* "frequency": "weekly",
|
||||
* "id": "b207a15e-8bfd-4aa7-9e64-1ba89699da06"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* @apiUse ChallengeNotFound
|
||||
* @apiUse UserNotFound
|
||||
*/
|
||||
@@ -478,25 +606,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 +638,7 @@ api.sendPrivateMessage = {
|
||||
);
|
||||
}
|
||||
|
||||
res.respond(200, {});
|
||||
res.respond(200, { message: newMessage });
|
||||
},
|
||||
};
|
||||
|
||||
@@ -519,7 +647,7 @@ api.sendPrivateMessage = {
|
||||
* @apiName TransferGems
|
||||
* @apiGroup Member
|
||||
*
|
||||
* @apiParam (Body) {String} message The message
|
||||
* @apiParam (Body) {String} message The message to the user
|
||||
* @apiParam (Body) {UUID} toUserId The toUser _id
|
||||
* @apiParam (Body) {Integer} gemAmount The number of gems to send
|
||||
*
|
||||
|
||||
@@ -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 = 'OFFICIAL CHALLENGE: BACK-TO-SCHOOL PREPARATION!';
|
||||
const LAST_ANNOUNCEMENT_TITLE = 'NEW BACKGROUNDS, ARMOIRE ITEMS, RESOLUTION SUCCESS CHALLENGE, AND TAKE THIS CHALLENGE';
|
||||
const worldDmg = { // @TODO
|
||||
bailey: false,
|
||||
};
|
||||
@@ -27,20 +27,39 @@ api.getNews = {
|
||||
html: `
|
||||
<div class="bailey">
|
||||
<div class="media align-items-center">
|
||||
<div class="mr-3 ${baileyClass}"></div>
|
||||
<div class="media-body">
|
||||
<h1 class="align-self-center">${res.t('newStuff')}</h1>
|
||||
<h2>8/8/2018 - ${LAST_ANNOUNCEMENT_TITLE}</h2>
|
||||
<div class="media align-items-center">
|
||||
<div class="mr-3 ${baileyClass}"></div>
|
||||
<div class="media-body">
|
||||
<h1 class="align-self-center">${res.t('newStuff')}</h1>
|
||||
<h2>9/4/2018 - ${LAST_ANNOUNCEMENT_TITLE}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<h3>September Backgrounds and Armoire Items!</h3>
|
||||
<p>We’ve added three new backgrounds to the Background Shop! Now your avatar can go Apple Picking, stand on a Giant Book, and hang out with your pets and mounts in a Cozy Barn. Check them out under User Icon > Backgrounds!</p>
|
||||
<p>Plus, there’s new gold-purchasable equipment in the Enchanted Armoire, including the Bookbinder Set. Better work hard on your real-life tasks to earn all the pieces! Enjoy :)</p>
|
||||
<div class="small mb-3">by GeraldThePixel, Maans, virginiamoon, shanaqui, and fasteagle190</div>
|
||||
</div>
|
||||
<div class="promo_armoire_backgrounds_201809 ml-3 mb-3"></div>
|
||||
</div>
|
||||
<div class="media align-items-center">
|
||||
<div class="scene_perfect_day mr-3"></div>
|
||||
<div class="media-body">
|
||||
<h3>September 2018 Resolution Success Challenge and New Take This Challenge</h3>
|
||||
<p>The Habitica team has launched a special official Challenge series hosted in the <a href='/groups/guild/6e6a8bd3-9f5f-4351-9188-9f11fcd80a99' target='_blank'>Official New Year's Resolution Guild</a>. These Challenges are designed to help you build and maintain goals that are destined for success and then stick with them as the year progresses. For this month's Challenge, <a href='https://habitica.com/challenges/8d08b298-1716-4553-8739-5071ae002de4' target='_blank'>Celebrate your Triumphs</a>, we're focusing on looking back to see all the progress you've made so far! It has a 15 Gem prize, which will be awarded to five lucky winners on October 1st.</p>
|
||||
<p>Congratulations to the winners of the August Challenge, Enkia the Wicked, wondergrrl, renko, Mibbs, and TereLiz!</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="media align-items-center">
|
||||
<div class="media-body">
|
||||
<p>The school year is looming large for many scholarly Habiticans, so we've prepared <a href='/challenges/0acb1d56-1660-41a4-af80-9259f080b62b' target='_blank'>a special Back-to-School Challenge</a> to help with the transition between summer and semester. Check it out now for a chance to win: five lucky winners will get a badge for their profile and their choice of a <a href='https://habitica.wikia.com/wiki/Subscription' target='_blank'>gift subscription</a> or Gems!</p>
|
||||
<div class="small mb-3">by Beffymaroo</div>
|
||||
<p>The next Take This Challenge has also launched, "<a href='/challenges/32818ef2-18de-48b6-ab6e-2b52640c47f7' target='_blank'>Gaining Inspiration Points</a>", with a focus on creative endeavors. Be sure to check it out to earn additional pieces of the Take This armor set!</p>
|
||||
</div>
|
||||
<div class="scene_reading ml-3 mb-3"></div>
|
||||
<div class="promo_take_this mb-3"></div>
|
||||
</div>
|
||||
<p><a href='http://www.takethis.org/' target='_blank'>Take This</a> is a nonprofit that seeks to inform the gamer community about mental health issues, to provide education about mental disorders and mental illness prevention, and to reduce the stigma of mental illness.</p>
|
||||
<p>Congratulations to the winners of the last Take This Challenge, "Notice Me, Senpai!": grand prize winner Sebem.seme, and runners-up Jessie, MaxClayson, kayote, Madison Walrath, and LaChistosa. Plus, all participants in that Challenge have received a piece of the <a href='http://habitica.wikia.com/wiki/Event_Item_Sequences#Take_This_Armor_Set' target='_blank'>Take This item set</a> if they hadn't completed it already. It is located in your Rewards column. Enjoy!</p>
|
||||
<div class="small mb-3">by Doctor B, the Take This team, Lemoness, Beffymaroo, and SabreCat</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -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'));
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user