mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-08-04 09:39:23 +00:00
Merge branch 'api-v3' into sabrecat/v3-payments
This commit is contained in:
commit
e980b3ce0a
35 changed files with 231 additions and 153 deletions
|
|
@ -49,7 +49,7 @@ module.exports = function randomDrop (user, modifiers, req = {}) {
|
|||
user.markModified('party.quest.progress');
|
||||
}
|
||||
|
||||
if (user.purchased && user.purchased.plan && user.purchased.plan.custsomerId) {
|
||||
if (user.purchased && user.purchased.plan && user.purchased.plan.customerId) {
|
||||
dropMultiplier = 2;
|
||||
} else {
|
||||
dropMultiplier = 1;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ var mongoose = require('mongoose');
|
|||
var _ = require('lodash');
|
||||
var uuid = require('uuid');
|
||||
var consoleStamp = require('console-stamp');
|
||||
var fs = require('fs');
|
||||
|
||||
// Add timestamps to console messages
|
||||
consoleStamp(console);
|
||||
|
|
@ -48,6 +49,8 @@ var BATCH_SIZE = 1000;
|
|||
var processedChallenges = 0;
|
||||
var totoalProcessedTasks = 0;
|
||||
|
||||
var newTasksIds = {}; // a map of old id -> [new id, challengeId]
|
||||
|
||||
// Only process challenges that fall in a interval ie -> up to 0000-4000-0000-0000
|
||||
var AFTER_CHALLENGE_ID = nconf.get('AFTER_CHALLENGE_ID');
|
||||
var BEFORE_CHALLENGE_ID = nconf.get('BEFORE_CHALLENGE_ID');
|
||||
|
|
@ -109,23 +112,42 @@ function processChallenges (afterId) {
|
|||
if (!oldChallenge.group) throw new Error('challenge.group is required');
|
||||
if (!oldChallenge.leader) throw new Error('challenge.leader is required');
|
||||
|
||||
|
||||
if (oldChallenge.leader === '9') {
|
||||
oldChallenge.leader = '00000000-0000-4000-9000-000000000000';
|
||||
}
|
||||
|
||||
if (oldChallenge.group === 'habitrpg') {
|
||||
oldChallenge.group = '00000000-0000-4000-A000-000000000000';
|
||||
}
|
||||
|
||||
delete oldChallenge.id;
|
||||
|
||||
var newChallenge = new NewChallenge(oldChallenge);
|
||||
|
||||
newChallenge.createdAt = createdAt;
|
||||
|
||||
oldTasks.forEach(function (oldTask) {
|
||||
oldTask._id = uuid.v4(); // TODO keep the old uuid unless duplicated
|
||||
oldTask._id = uuid.v4();
|
||||
oldTask.legacyId = oldTask.id; // store the old task id
|
||||
delete oldTask.id;
|
||||
|
||||
oldTask.challenge = oldTask.challenge || {};
|
||||
oldTask.challenge.id = newChallenge._id;
|
||||
|
||||
if (newTasksIds[oldTask.legacyId + '-' + newChallenge._id]) {
|
||||
throw new Error('duplicate :(');
|
||||
} else {
|
||||
newTasksIds[oldTask.legacyId + '-' + newChallenge._id] = oldTask._id;
|
||||
}
|
||||
|
||||
oldTask.tags = _.map(oldTask.tags || {}, function (tagPresent, tagId) {
|
||||
return tagPresent && tagId;
|
||||
});
|
||||
|
||||
if (!oldTask.text) oldTask.text = 'task text'; // required
|
||||
|
||||
oldTask.challenge = oldTask.challenge || {};
|
||||
oldTask.challenge.id = oldChallenge._id;
|
||||
oldTask.createdAt = oldTask.dateCreated;
|
||||
|
||||
newChallenge.tasksOrder[`${oldTask.type}s`].push(oldTask._id);
|
||||
if (oldTask.completed) oldTask.completed = false;
|
||||
|
|
@ -155,6 +177,8 @@ function processChallenges (afterId) {
|
|||
if (lastChallenge) {
|
||||
return processChallenges(lastChallenge);
|
||||
} else {
|
||||
console.log('Writing newTasksIds.json...')
|
||||
fs.writeFileSync('newTasksIds.json', JSON.stringify(newTasksIds, null, 4), 'utf8');
|
||||
return console.log('Done!');
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -87,8 +87,15 @@ function processChallenges (afterId) {
|
|||
}
|
||||
|
||||
oldChallenges.forEach(function (oldChallenge) {
|
||||
// Tyler Renelle
|
||||
oldChallenge.members.forEach(function (id, index) {
|
||||
if (id === '9') {
|
||||
oldChallenge.members[index] = '00000000-0000-4000-9000-000000000000';
|
||||
}
|
||||
});
|
||||
|
||||
promises.push(newUserCollection.updateMany({
|
||||
_id: {$in: oldChallenge.members},
|
||||
_id: {$in: oldChallenge.members || []},
|
||||
}, {
|
||||
$push: {challenges: oldChallenge._id},
|
||||
}, {multi: true}));
|
||||
|
|
|
|||
|
|
@ -102,9 +102,11 @@ function processGroups (afterId) {
|
|||
}
|
||||
|
||||
oldGroups.forEach(function (oldGroup) {
|
||||
if ((!oldGroup.privacy || oldGroup.privacy === 'private') && (!oldGroup.members || oldGroup.members.length === 0)) return; // delete empty private groups
|
||||
if ((!oldGroup.privacy || oldGroup.privacy === 'private') && (!oldGroup.members || oldGroup.members.length === 0)) return; // delete empty private groups TODO must also delete challenges or this won't work
|
||||
|
||||
oldGroup.members = oldGroup.members || [];
|
||||
oldGroup.memberCount = oldGroup.members ? oldGroup.members.length : 0;
|
||||
oldGroup.memberCount = oldGroup.challenges ? oldGroup.challenges.length : 0;
|
||||
oldGroup.challengeCount = oldGroup.challenges ? oldGroup.challenges.length : 0;
|
||||
|
||||
if (!oldGroup.balance <= 0) oldGroup.balance = 0;
|
||||
if (!oldGroup.name) oldGroup.name = 'group name';
|
||||
|
|
@ -132,7 +134,7 @@ function processGroups (afterId) {
|
|||
|
||||
if (!oldGroup.privacy) {
|
||||
// throw new Error('group.privacy is required');
|
||||
group.privacy = 'private';
|
||||
oldGroup.privacy = 'private';
|
||||
}
|
||||
|
||||
var updateMembers = {};
|
||||
|
|
@ -144,6 +146,13 @@ function processGroups (afterId) {
|
|||
}
|
||||
|
||||
if (oldGroup.members) {
|
||||
// Tyler Renelle
|
||||
oldGroup.members.forEach(function (id, index) {
|
||||
if (id === '9') {
|
||||
oldGroup.members[index] = '00000000-0000-4000-9000-000000000000';
|
||||
}
|
||||
});
|
||||
|
||||
promises.push(newUserCollection.updateMany({
|
||||
_id: {$in: oldGroup.members},
|
||||
}, updateMembers, {multi: true}));
|
||||
|
|
|
|||
|
|
@ -1,52 +1,52 @@
|
|||
/*
|
||||
DEFINE BEFORE MIGRATING
|
||||
|
||||
tasks: userId (sparse?), challenge.id (sparse), challenge.taskId (sparse), type? completed?
|
||||
tasks: userId OK (sparse?), challenge.id OK (sparse?), challenge.taskId OK (sparse?), type? completed?
|
||||
users:
|
||||
id & apiToken?,
|
||||
auth.facebook.emails.value -> unique and sparse?,
|
||||
auth.facebook.id - unique and sparse,
|
||||
auth.local.email - unique and sparse,
|
||||
auth.local.lowerCaseUsername,
|
||||
auth.local.username - unique and sparse
|
||||
id & apiToken, OK
|
||||
auth.facebook.emails.value OK -> unique and sparse?,
|
||||
auth.facebook.id - unique and sparse, OK
|
||||
auth.local.email - unique and sparse, OK
|
||||
auth.local.lowerCaseUsername, OK
|
||||
auth.local.username - unique OK
|
||||
auth.local.username & auth.local.hashed_password?,
|
||||
auth.timestamps.created?,
|
||||
auth.timestamps.loggedin?,
|
||||
backer.tier -1
|
||||
auth.timestamps.created?, OK
|
||||
auth.timestamps.loggedin?, OK
|
||||
backer.tier -1 OK
|
||||
{ "contributor.admin" : 1 , "contributor.level" : -1 , "backer.npc" : -1 , "profile.name" : 1}
|
||||
{ "contributor.admin" : 1.0}
|
||||
{ "contributor.level" : 1.0}
|
||||
{ "contributor.admin" : 1.0} NO, see ^
|
||||
{ "contributor.level" : 1.0} OK
|
||||
{ "contributor.level" : 1.0 , "purchased.plan.customerId" : 1.0} ?
|
||||
{ "flags.lastWeeklyRecap" : 1 , "_id" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "preferences.emailNotifications.weeklyRecaps" : 1}
|
||||
{ "invitations.guilds.id" : 1}
|
||||
{ "invitations.party.id" : 1}
|
||||
{ "preferences.sleep" : 1 , "_id" : 1 , "flags.lastWeeklyRecap" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "preferences.emailNotifications.weeklyRecaps" : 1}
|
||||
{ "preferences.sleep" : 1 , "_id" : 1 , "lastCron" : 1 , "preferences.emailNotifications.importantAnnouncements" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "flags.recaptureEmailsPhase" : 1}
|
||||
profile.name ?
|
||||
{ "purchased.plan.customerId" : 1.0}
|
||||
{ "purchased.plan.paymentMethod" : 1.0}
|
||||
NO { "flags.lastWeeklyRecap" : 1 , "_id" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "preferences.emailNotifications.weeklyRecaps" : 1}
|
||||
{ "invitations.guilds.id" : 1} OK
|
||||
{ "invitations.party.id" : 1} OK
|
||||
OK { "preferences.sleep" : 1 , "_id" : 1 , "flags.lastWeeklyRecap" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "preferences.emailNotifications.weeklyRecaps" : 1}
|
||||
OK { "preferences.sleep" : 1 , "_id" : 1 , "lastCron" : 1 , "preferences.emailNotifications.importantAnnouncements" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "flags.recaptureEmailsPhase" : 1}
|
||||
profile.name ? OK
|
||||
{ "purchased.plan.customerId" : 1.0} OK
|
||||
{ "purchased.plan.paymentMethod" : 1.0} OK
|
||||
|
||||
guilds
|
||||
party.id
|
||||
challenges
|
||||
guilds OK
|
||||
party.id OK
|
||||
challenges OK
|
||||
challenges:
|
||||
{ "_id" : 1.0 , "__v" : 1.0} ?
|
||||
{ "_id" : 1.0 , "__v" : 1.0} ? NO
|
||||
{ "_id" : 1.0 , "official" : -1.0 , "timestamp" : -1.0}
|
||||
{ "group" : 1.0 , "official" : -1.0 , "timestamp" : -1.0}
|
||||
{ "leader" : 1.0 , "official" : -1.0 , "timestamp" : -1.0}
|
||||
{ "members" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} ?
|
||||
{ "official" : -1 , "timestamp" : -1}
|
||||
{ "group" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} OK
|
||||
{ "leader" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} OK
|
||||
{ "members" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} ? NO
|
||||
{ "official" : -1 , "timestamp" : -1} ?
|
||||
{ "official" : -1 , "timestamp" : -1, "_id": 1} ?
|
||||
groups:
|
||||
{ "_id" : 1 , "quest.key" : 1}
|
||||
{ "_id" : 1 , "quest.key" : 1} ?
|
||||
{ "_id" : 1.0 , "__v" : 1.0} ?
|
||||
{ "_id" : 1.0 , "privacy" : 1.0 , "members" : 1.0} ?
|
||||
{ "members" : 1.0 , "type" : 1.0 , "memberCount" : -1.0} ?
|
||||
{ "members" : 1} ?
|
||||
{ "_id" : 1.0 , "privacy" : 1.0 , "members" : 1.0} ? NO
|
||||
{ "members" : 1.0 , "type" : 1.0 , "memberCount" : -1.0} ? NO
|
||||
{ "members" : 1} ? NO
|
||||
{ "privacy" : 1.0 , "memberCount" : -1.0} ?
|
||||
{ "privacy" : 1.0} ?
|
||||
{ "privacy" : 1.0} OK
|
||||
{ "type" : 1 , "privacy" : 1} ?
|
||||
{ "type" : 1.0 , "members" : 1.0} ?
|
||||
{ "type" : 1} ?
|
||||
emailUnsubscriptions: email unique
|
||||
{ "type" : 1.0 , "members" : 1.0} ? NO
|
||||
{ "type" : 1} ? OK
|
||||
emailUnsubscriptions: email unique OK
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ var _ = require('lodash');
|
|||
var uuid = require('uuid');
|
||||
var consoleStamp = require('console-stamp');
|
||||
var common = require('../../common');
|
||||
var moment = require('moment');
|
||||
|
||||
// Add timestamps to console messages
|
||||
consoleStamp(console);
|
||||
|
|
@ -51,23 +52,23 @@ var BATCH_SIZE = 1000;
|
|||
var processedUsers = 0;
|
||||
var totoalProcessedTasks = 0;
|
||||
|
||||
var challengeTaskWithMatchingId = 0;
|
||||
var challengeTaskNoMatchingId = 0;
|
||||
|
||||
// Load the new tasks ids for challenges tasks
|
||||
var newTasksIds = require('./newTasksIds.json');
|
||||
|
||||
// Only process users that fall in a interval ie up to -> 0000-4000-0000-0000
|
||||
var AFTER_USER_ID = nconf.get('AFTER_USER_ID');
|
||||
var BEFORE_USER_ID = nconf.get('BEFORE_USER_ID');
|
||||
|
||||
/* TODO compare old and new model
|
||||
- _id 9
|
||||
- challenges
|
||||
- groups
|
||||
- invitations
|
||||
- challenges' tasks
|
||||
*/
|
||||
|
||||
function processUsers (afterId) {
|
||||
var processedTasks = 0;
|
||||
var lastUser = null;
|
||||
var oldUsers;
|
||||
|
||||
var now = new Date();
|
||||
|
||||
var query = {};
|
||||
|
||||
if (BEFORE_USER_ID) {
|
||||
|
|
@ -110,6 +111,8 @@ function processUsers (afterId) {
|
|||
delete oldUser.rewards;
|
||||
delete oldUser.todos;
|
||||
|
||||
delete oldUser.id;
|
||||
|
||||
oldUser.tags = oldUser.tags.map(function (tag) {
|
||||
return {
|
||||
id: tag.id,
|
||||
|
|
@ -123,6 +126,7 @@ function processUsers (afterId) {
|
|||
}
|
||||
|
||||
var newUser = new NewUser(oldUser);
|
||||
var isSubscribed = newUser.isSubscribed();
|
||||
|
||||
oldTasks.forEach(function (oldTask) {
|
||||
oldTask._id = uuid.v4(); // create a new unique uuid
|
||||
|
|
@ -132,10 +136,31 @@ function processUsers (afterId) {
|
|||
|
||||
oldTask.challenge = oldTask.challenge || {};
|
||||
if (oldTask.challenge.id) {
|
||||
oldTask.challenge.taskId = oldTask.legacyId;
|
||||
if (oldTask.challenge.broken) {
|
||||
oldTask.challenge.taskId = oldTask.legacyId;
|
||||
} else {
|
||||
var newId = newTasksIds[oldTask.legacyId + '-' + oldTask.challenge.id];
|
||||
|
||||
// Challenges' tasks ids changed
|
||||
if (!newId && !oldTask.challenge.broken) {
|
||||
challengeTaskNoMatchingId++;
|
||||
oldTask.challenge.taskId = oldTask.legacyId;
|
||||
oldTask.challenge.broken = 'CHALLENGE_TASK_NOT_FOUND';
|
||||
} else {
|
||||
challengeTaskWithMatchingId++;
|
||||
oldTask.challenge.taskId = newId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oldTask.createdAt = old.dateCreated;
|
||||
// Delete old completed todos
|
||||
if (oldTask.type === 'todo' && oldTask.completed && (!oldTask.challenge.id || oldTask.challenge.broken)) {
|
||||
if (moment(now).subtract(isSubscribed ? 90 : 30, 'days').toDate() > moment(oldTask.dateCompleted).toDate()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
oldTask.createdAt = oldTask.dateCreated;
|
||||
|
||||
if (!oldTask.text) oldTask.text = 'task text'; // required
|
||||
oldTask.tags = _.map(oldTask.tags, function (tagPresent, tagId) {
|
||||
|
|
@ -146,7 +171,7 @@ function processUsers (afterId) {
|
|||
newUser.tasksOrder[`${oldTask.type}s`].push(oldTask._id);
|
||||
}
|
||||
|
||||
var allTasksFields = ['_id', 'type', 'text', 'notes', 'tags', 'value', 'priority', 'attribute', 'challenge', 'reminders'];
|
||||
var allTasksFields = ['_id', 'type', 'text', 'notes', 'tags', 'value', 'priority', 'attribute', 'challenge', 'reminders', 'userId', 'legacyId'];
|
||||
// using mongoose models is too slow
|
||||
if (oldTask.type === 'habit') {
|
||||
oldTask = _.pick(oldTask, allTasksFields.concat(['history', 'up', 'down']));
|
||||
|
|
@ -179,6 +204,8 @@ function processUsers (afterId) {
|
|||
processedUsers += oldUsers.length;
|
||||
|
||||
console.log(`Saved ${oldUsers.length} users and their tasks.`);
|
||||
console.log('Challenges\' tasks no matching id: ', challengeTaskNoMatchingId);
|
||||
console.log('Challenges\' tasks with matching id: ', challengeTaskWithMatchingId);
|
||||
|
||||
if (lastUser) {
|
||||
return processUsers(lastUser);
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@
|
|||
"nock": "^2.17.0",
|
||||
"phantomjs": "^1.9",
|
||||
"protractor": "^3.1.1",
|
||||
"require-again": "^1.0.1",
|
||||
"rewire": "^2.3.3",
|
||||
"rimraf": "^2.4.3",
|
||||
"shelljs": "^0.5.3",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ import {
|
|||
|
||||
describe('GET /groups', () => {
|
||||
let user;
|
||||
const NUMBER_OF_PUBLIC_GUILDS = 3;
|
||||
const NUMBER_OF_PUBLIC_GUILDS = 3; // 2 + the tavern
|
||||
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1;
|
||||
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
|
||||
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5;
|
||||
|
||||
|
|
@ -87,6 +88,11 @@ describe('GET /groups', () => {
|
|||
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS);
|
||||
});
|
||||
|
||||
it('returns all the user\'s guilds when guilds passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=guilds'))
|
||||
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER + NUMBER_OF_USERS_PRIVATE_GUILDS);
|
||||
});
|
||||
|
||||
it('returns all private guilds user is a part of when privateGuilds passed in as query', async () => {
|
||||
await expect(user.get('/groups?type=privateGuilds'))
|
||||
.to.eventually.have.a.lengthOf(NUMBER_OF_USERS_PRIVATE_GUILDS);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import request from 'request';
|
|||
import nconf from 'nconf';
|
||||
import nodemailer from 'nodemailer';
|
||||
import Q from 'q';
|
||||
import requireAgain from 'require-again';
|
||||
import logger from '../../../../../website/src/libs/api-v3/logger';
|
||||
|
||||
function getUser () {
|
||||
|
|
@ -34,10 +35,6 @@ function getUser () {
|
|||
describe('emails', () => {
|
||||
let pathToEmailLib = '../../../../../website/src/libs/api-v3/email';
|
||||
|
||||
beforeEach(() => {
|
||||
delete require.cache[require.resolve(pathToEmailLib)];
|
||||
});
|
||||
|
||||
describe('sendEmail', () => {
|
||||
it('can send an email using the default transport', () => {
|
||||
let sendMailSpy = sandbox.stub().returns(Q.defer().promise);
|
||||
|
|
@ -46,7 +43,7 @@ describe('emails', () => {
|
|||
sendMail: sendMailSpy,
|
||||
});
|
||||
|
||||
let attachEmail = require(pathToEmailLib);
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
attachEmail.send();
|
||||
expect(sendMailSpy).to.be.calledOnce;
|
||||
});
|
||||
|
|
@ -60,7 +57,7 @@ describe('emails', () => {
|
|||
});
|
||||
sandbox.stub(logger, 'error');
|
||||
|
||||
let attachEmail = require(pathToEmailLib);
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
attachEmail.send();
|
||||
expect(sendMailSpy).to.be.calledOnce;
|
||||
deferred.reject();
|
||||
|
|
@ -75,13 +72,13 @@ describe('emails', () => {
|
|||
|
||||
describe('getUserInfo', () => {
|
||||
it('returns an empty object if no field request', () => {
|
||||
let attachEmail = require(pathToEmailLib);
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
let getUserInfo = attachEmail.getUserInfo;
|
||||
expect(getUserInfo({}, [])).to.be.empty;
|
||||
});
|
||||
|
||||
it('returns correct user data', () => {
|
||||
let attachEmail = require(pathToEmailLib);
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
let getUserInfo = attachEmail.getUserInfo;
|
||||
let user = getUser();
|
||||
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||
|
|
@ -93,7 +90,7 @@ describe('emails', () => {
|
|||
});
|
||||
|
||||
it('returns correct user data [facebook users]', () => {
|
||||
let attachEmail = require(pathToEmailLib);
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
let getUserInfo = attachEmail.getUserInfo;
|
||||
let user = getUser();
|
||||
delete user.profile.name;
|
||||
|
|
@ -108,7 +105,7 @@ describe('emails', () => {
|
|||
});
|
||||
|
||||
it('has fallbacks for missing data', () => {
|
||||
let attachEmail = require(pathToEmailLib);
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
let getUserInfo = attachEmail.getUserInfo;
|
||||
let user = getUser();
|
||||
delete user.profile.name;
|
||||
|
|
@ -135,7 +132,7 @@ describe('emails', () => {
|
|||
|
||||
it('can send a txn email to one recipient', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||
let attachEmail = require(pathToEmailLib);
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
let sendTxnEmail = attachEmail.sendTxn;
|
||||
let emailType = 'an email type';
|
||||
let mailingInfo = {
|
||||
|
|
@ -158,7 +155,7 @@ describe('emails', () => {
|
|||
|
||||
it('does not send email if address is missing', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||
let attachEmail = require(pathToEmailLib);
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
let sendTxnEmail = attachEmail.sendTxn;
|
||||
let emailType = 'an email type';
|
||||
let mailingInfo = {
|
||||
|
|
@ -172,7 +169,7 @@ describe('emails', () => {
|
|||
|
||||
it('uses getUserInfo in case of user data', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||
let attachEmail = require(pathToEmailLib);
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
let sendTxnEmail = attachEmail.sendTxn;
|
||||
let emailType = 'an email type';
|
||||
let mailingInfo = getUser();
|
||||
|
|
@ -190,7 +187,7 @@ describe('emails', () => {
|
|||
|
||||
it('sends email with some default variables', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||
let attachEmail = require(pathToEmailLib);
|
||||
let attachEmail = requireAgain(pathToEmailLib);
|
||||
let sendTxnEmail = attachEmail.sendTxn;
|
||||
let emailType = 'an email type';
|
||||
let mailingInfo = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import winston from 'winston';
|
||||
import requireAgain from 'require-again';
|
||||
|
||||
/* eslint-disable global-require */
|
||||
describe('logger', () => {
|
||||
|
|
@ -7,8 +8,6 @@ describe('logger', () => {
|
|||
let errorSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
delete require.cache[require.resolve(pathToLoggerLib)];
|
||||
|
||||
infoSpy = sandbox.stub();
|
||||
errorSpy = sandbox.stub();
|
||||
sandbox.stub(winston, 'Logger').returns({
|
||||
|
|
@ -22,7 +21,7 @@ describe('logger', () => {
|
|||
});
|
||||
|
||||
it('info', () => {
|
||||
let attachLogger = require(pathToLoggerLib);
|
||||
let attachLogger = requireAgain(pathToLoggerLib);
|
||||
attachLogger.info(1, 2, 3);
|
||||
expect(infoSpy).to.be.calledOnce;
|
||||
expect(infoSpy).to.be.calledWith(1, 2, 3);
|
||||
|
|
@ -30,14 +29,14 @@ describe('logger', () => {
|
|||
|
||||
describe('error', () => {
|
||||
it('with custom arguments', () => {
|
||||
let attachLogger = require(pathToLoggerLib);
|
||||
let attachLogger = requireAgain(pathToLoggerLib);
|
||||
attachLogger.error(1, 2, 3, 4);
|
||||
expect(errorSpy).to.be.calledOnce;
|
||||
expect(errorSpy).to.be.calledWith(1, 2, 3, 4);
|
||||
});
|
||||
|
||||
it('with error', () => {
|
||||
let attachLogger = require(pathToLoggerLib);
|
||||
let attachLogger = requireAgain(pathToLoggerLib);
|
||||
let errInstance = new Error('An error.');
|
||||
attachLogger.error(errInstance, {
|
||||
data: 1,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
} from '../../../../helpers/api-unit.helper';
|
||||
import analyticsService from '../../../../../website/src/libs/api-v3/analyticsService';
|
||||
import nconf from 'nconf';
|
||||
import requireAgain from 'require-again';
|
||||
|
||||
describe('analytics middleware', () => {
|
||||
let res, req, next;
|
||||
|
|
@ -17,15 +18,8 @@ describe('analytics middleware', () => {
|
|||
next = generateNext();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// The nconf.get('IS_PROD') occurs when the file is required
|
||||
// Since node caches IS_PROD, we have to delete it from the cache
|
||||
// to test prod vs non-prod behaviors
|
||||
delete require.cache[require.resolve(pathToAnalyticsMiddleware)];
|
||||
});
|
||||
|
||||
it('attaches analytics object res.locals', () => {
|
||||
let attachAnalytics = require(pathToAnalyticsMiddleware);
|
||||
let attachAnalytics = requireAgain(pathToAnalyticsMiddleware);
|
||||
|
||||
attachAnalytics(req, res, next);
|
||||
|
||||
|
|
@ -34,7 +28,7 @@ describe('analytics middleware', () => {
|
|||
|
||||
it('attaches stubbed methods for non-prod environments', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false);
|
||||
let attachAnalytics = require(pathToAnalyticsMiddleware);
|
||||
let attachAnalytics = requireAgain(pathToAnalyticsMiddleware);
|
||||
|
||||
attachAnalytics(req, res, next);
|
||||
|
||||
|
|
@ -45,7 +39,7 @@ describe('analytics middleware', () => {
|
|||
it('attaches real methods for prod environments', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||
|
||||
let attachAnalytics = require(pathToAnalyticsMiddleware);
|
||||
let attachAnalytics = requireAgain(pathToAnalyticsMiddleware);
|
||||
|
||||
attachAnalytics(req, res, next);
|
||||
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@ api.list = async function(req, res, next) {
|
|||
User.findById(chal.leader).select(nameFields).exec(),
|
||||
Group.findById(chal.group).select(basicGroupFields).exec(),
|
||||
]).then(populatedData => {
|
||||
resChals[index].leader = populatedData[0].toJSON({minimize: true});
|
||||
resChals[index].group = populatedData[1].toJSON({minimize: true});
|
||||
resChals[index].leader = populatedData[0] ? populatedData[0].toJSON({minimize: true}) : null;
|
||||
resChals[index].group = populatedData[1] ? populatedData[1].toJSON({minimize: true}) : null;
|
||||
});
|
||||
}));
|
||||
|
||||
|
|
@ -88,7 +88,8 @@ api.get = async function(req, res, next) {
|
|||
let group = await Group.getGroup({user, groupId: challenge.group, optionalMembership: true});
|
||||
if (!group || !challenge.canView(user, group)) return res.status(404).json({err: 'Challenge ' + req.params.cid + ' not found'});
|
||||
|
||||
let leaderRes = (await User.findById(challenge.leader).select('profile.name').exec()).toJSON({minimize: true});
|
||||
let leaderRes = await User.findById(challenge.leader).select('profile.name').exec();
|
||||
leaderRes = leaderRes ? leaderRes.toJSON({minimize: true}) : null;
|
||||
|
||||
challenge.getTransformedData({
|
||||
populateMembers: 'profile.name',
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ api.registerLocal = {
|
|||
newUser = fbUser;
|
||||
} else {
|
||||
newUser = new User(newUser);
|
||||
newUser.registeredThrough = req.headers['x-client']; // TODO is this saved somewhere?
|
||||
newUser.registeredThrough = req.headers['x-client']; // Not saved, used to create the correct tasks based on the device used
|
||||
}
|
||||
|
||||
// we check for partyInvite for backward compatibility
|
||||
|
|
|
|||
|
|
@ -153,7 +153,8 @@ api.joinChallenge = {
|
|||
type: group.type,
|
||||
privacy: group.privacy,
|
||||
};
|
||||
response.leader = (await User.findById(response.leader).select(nameFields).exec()).toJSON({minimize: true});
|
||||
let chalLeader = await User.findById(response.leader).select(nameFields).exec();
|
||||
response.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null;
|
||||
|
||||
res.respond(200, response);
|
||||
},
|
||||
|
|
@ -233,8 +234,8 @@ api.getUserChallenges = {
|
|||
User.findById(chal.leader).select(nameFields).exec(),
|
||||
Group.findById(chal.group).select(basicGroupFields).exec(),
|
||||
]).then(populatedData => {
|
||||
resChals[index].leader = populatedData[0].toJSON({minimize: true});
|
||||
resChals[index].group = populatedData[1].toJSON({minimize: true});
|
||||
resChals[index].leader = populatedData[0] ? populatedData[0].toJSON({minimize: true}) : null;
|
||||
resChals[index].group = populatedData[1] ? populatedData[1].toJSON({minimize: true}) : null;
|
||||
});
|
||||
}));
|
||||
|
||||
|
|
@ -278,7 +279,7 @@ api.getGroupChallenges = {
|
|||
// Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
|
||||
await Q.all(resChals.map((chal, index) => {
|
||||
return User.findById(chal.leader).select(nameFields).exec().then(populatedLeader => {
|
||||
resChals[index].leader = populatedLeader.toJSON({minimize: true});
|
||||
resChals[index].leader = populatedLeader ? populatedLeader.toJSON({minimize: true}) : null;
|
||||
});
|
||||
}));
|
||||
|
||||
|
|
@ -322,7 +323,8 @@ api.getChallenge = {
|
|||
let chalRes = challenge.toJSON();
|
||||
chalRes.group = group.toJSON({minimize: true});
|
||||
// Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
|
||||
chalRes.leader = (await User.findById(chalRes.leader).select(nameFields).exec()).toJSON({minimize: true});
|
||||
let chalLeader = await User.findById(chalRes.leader).select(nameFields).exec();
|
||||
chalRes.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null;
|
||||
|
||||
res.respond(200, chalRes);
|
||||
},
|
||||
|
|
@ -441,7 +443,8 @@ api.updateChallenge = {
|
|||
type: group.type,
|
||||
privacy: group.privacy,
|
||||
};
|
||||
response.leader = (await User.findById(response.leader).select(nameFields).exec()).toJSON({minimize: true});
|
||||
let chalLeader = await User.findById(response.leader).select(nameFields).exec();
|
||||
response.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null;
|
||||
res.respond(200, response);
|
||||
},
|
||||
};
|
||||
|
|
@ -475,7 +478,7 @@ export async function _closeChal (challenge, broken = {}) {
|
|||
]);
|
||||
}
|
||||
|
||||
sendPushNotification(savedWinner, shared.i18n.t('wonChallenge'), challenge.name); // TODO translate
|
||||
sendPushNotification(savedWinner, shared.i18n.t('wonChallenge'), challenge.name);
|
||||
}
|
||||
|
||||
// Run some operations in the background withouth blocking the thread
|
||||
|
|
@ -501,7 +504,7 @@ export async function _closeChal (challenge, broken = {}) {
|
|||
}, {multi: true}).exec(),
|
||||
];
|
||||
|
||||
Q.allSettled(backgroundTasks); // TODO look if allSettled could be useful somewhere else
|
||||
Q.all(backgroundTasks);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -510,7 +513,7 @@ export async function _closeChal (challenge, broken = {}) {
|
|||
* @apiName DeleteChallenge
|
||||
* @apiGroup Challenge
|
||||
*
|
||||
* challengeId {UUID} The _id for the challenge to delete
|
||||
* @apiParam {UUID} challengeId The _id for the challenge to delete
|
||||
*
|
||||
* @apiSuccess {object} data An empty object
|
||||
*/
|
||||
|
|
@ -542,8 +545,8 @@ api.deleteChallenge = {
|
|||
* @apiName SelectChallengeWinner
|
||||
* @apiGroup Challenge
|
||||
*
|
||||
* challengeId {UUID} The _id for the challenge to close with a winner
|
||||
* winnerId {UUID} The _id of the winning user
|
||||
* @apiParam {UUID} challengeId The _id for the challenge to close with a winner
|
||||
* @apiParam {UUID} winnerId The _id of the winning user
|
||||
*
|
||||
* @apiSuccess {object} data An empty object
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import _ from 'lodash';
|
|||
import { removeFromArray } from '../../libs/api-v3/collectionManipulators';
|
||||
import { sendTxn } from '../../libs/api-v3/email';
|
||||
import nconf from 'nconf';
|
||||
import Q from 'q';
|
||||
|
||||
import setupNconf from '../../libs/api-v3/setupNconf';
|
||||
setupNconf();
|
||||
|
||||
|
|
@ -89,12 +91,14 @@ api.postChat = {
|
|||
|
||||
group.sendChat(req.body.message, user);
|
||||
|
||||
let toSave = [group.save()];
|
||||
|
||||
if (group.type === 'party') {
|
||||
user.party.lastMessageSeen = group.chat[0].id;
|
||||
user.save(); // TODO why this is non-blocking? must catch?
|
||||
toSave.push(user.save());
|
||||
}
|
||||
|
||||
let savedGroup = await group.save();
|
||||
let [savedGroup] = await Q.all(toSave);
|
||||
if (chatUpdated) {
|
||||
res.respond(200, {chat: Group.toJSONCleanChat(savedGroup, user).chat});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ api.createGroup = {
|
|||
* @apiName GetGroups
|
||||
* @apiGroup Group
|
||||
*
|
||||
* @apiParam {string} type The type of groups to retrieve. Must be a query string representing a list of values like 'tavern,party'. Possible values are party, privateGuilds, publicGuilds, tavern
|
||||
* @apiParam {string} type The type of groups to retrieve. Must be a query string representing a list of values like 'tavern,party'. Possible values are party, guilds, privateGuilds, publicGuilds, tavern
|
||||
*
|
||||
* @apiSuccess {Array} data An array of the requested groups
|
||||
*/
|
||||
|
|
@ -95,7 +95,6 @@ api.getGroups = {
|
|||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
// TODO validate types are acceptable? probably not necessary
|
||||
let types = req.query.type.split(',');
|
||||
let groupFields = basicGroupFields.concat('description memberCount balance');
|
||||
let sort = '-memberCount';
|
||||
|
|
@ -444,7 +443,7 @@ api.removeGroupMember = {
|
|||
group.quest.leader = undefined;
|
||||
} else if (group.quest && group.quest.members) {
|
||||
// remove member from quest
|
||||
group.quest.members[member._id] = undefined; // TODO remmeber to check these are mark modified everywhere
|
||||
group.quest.members[member._id] = undefined;
|
||||
group.markModified('quest.members');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
sendTxn as sendTxnEmail,
|
||||
} from '../../libs/api-v3/email';
|
||||
import Q from 'q';
|
||||
import sendPushNotification from '../../libs/api-v3/pushNotifications';
|
||||
|
||||
let api = {};
|
||||
|
||||
|
|
@ -349,8 +350,7 @@ api.transferGems = {
|
|||
]);
|
||||
}
|
||||
|
||||
// TODO: Add push notifications
|
||||
// pushNotify.sendNotify(sender, res.t('giftedGems'), res.t('giftedGemsInfo', { amount: gemAmount, name: byUsername }));
|
||||
sendPushNotification(sender, res.t('giftedGems'), res.t('giftedGemsInfo', { amount: gemAmount, name: byUsername }));
|
||||
|
||||
res.respond(200, {});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ api.createUserTasks = {
|
|||
*/
|
||||
api.createChallengeTasks = {
|
||||
method: 'POST',
|
||||
url: '/tasks/challenge/:challengeId', // TODO should be /tasks/challengeS/:challengeId ? plural?
|
||||
url: '/tasks/challenge/:challengeId',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
||||
|
|
@ -303,7 +303,6 @@ api.updateTask = {
|
|||
}
|
||||
|
||||
// we have to convert task to an object because otherwise things don't get merged correctly. Bad for performances?
|
||||
// TODO regarding comment above, make sure other models with nested fields are using this trick too
|
||||
let [updatedTaskObj] = common.ops.updateTask(task.toObject(), req);
|
||||
_.assign(task, Tasks.Task.sanitize(updatedTaskObj));
|
||||
// console.log(task.modifiedPaths(), task.toObject().repeat === tep)
|
||||
|
|
@ -360,7 +359,7 @@ api.scoreTask = {
|
|||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('direction', res.t('directionUpDown')).notEmpty().isIn(['up', 'down']); // TODO what about rewards? maybe separate route?
|
||||
req.checkParams('direction', res.t('directionUpDown')).notEmpty().isIn(['up', 'down']);
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
|
@ -389,7 +388,7 @@ api.scoreTask = {
|
|||
} else if (wasCompleted && !task.completed) {
|
||||
let hasTask = removeFromArray(user.tasksOrder.todos, task._id);
|
||||
if (!hasTask) {
|
||||
user.tasksOrder.todos.push(task._id); // TODO push at the top?
|
||||
user.tasksOrder.todos.push(task._id);
|
||||
} // If for some reason it hadn't been removed previously don't do anything
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ api.exportUserAvatarHtml = {
|
|||
if (!member) throw new NotFound(res.t('userWithIDNotFound', {userId: memberId}));
|
||||
res.render('avatar-static', {
|
||||
title: member.profile.name,
|
||||
env: _.defaults({member}, res.locals.habitrpg), // TODO review once static pages are done
|
||||
env: _.defaults({member}, res.locals.habitrpg),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ if (nconf.get('LOGGLY:enabled')){
|
|||
|
||||
if (!logger) {
|
||||
logger = new (winston.Logger)({});
|
||||
logger.add(winston.transports.Console, {colorize:true}); // TODO remove
|
||||
|
||||
if (nconf.get('NODE_ENV') !== 'production') {
|
||||
logger.add(winston.transports.Console, {colorize:true});
|
||||
logger.add(winston.transports.File, {filename: 'habitrpg.log'});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ export function cron (options = {}) {
|
|||
gaLabel: 'Cron Count',
|
||||
gaValue: user.flags.cronCount,
|
||||
uuid: user._id,
|
||||
user, // TODO is it really necessary passing the whole user object?
|
||||
user,
|
||||
resting: user.preferences.sleep,
|
||||
cronCount: user.flags.cronCount,
|
||||
progressUp: _.min([_progress.up, 900]),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ const logger = new winston.Logger();
|
|||
if (IS_PROD) {
|
||||
// TODO production logging, use loggly and new relic too
|
||||
// log errors to console too
|
||||
logger
|
||||
.add(winston.transports.Console, {
|
||||
colorize: true,
|
||||
prettyPrint: true,
|
||||
});
|
||||
} else if (IS_TEST) {
|
||||
// Do not log anything when testing
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@ Subscribers and challenges:
|
|||
- 1 value each year for the previous years
|
||||
*/
|
||||
export function preenHistory (history, isSubscribed, timezoneOffset) {
|
||||
// history = _.filter(history, historyEntry => Boolean(historyEntry)); // Filter missing entries TODO add to migration
|
||||
// history = _.filter(history, historyEntry => Boolean(historyEntry)); // Filter missing entries
|
||||
let now = timezoneOffset ? moment().zone(timezoneOffset) : moment();
|
||||
// Date after which to begin compressing data
|
||||
let cutOff = now.subtract(isSubscribed ? 365 : 60, 'days').startOf('day');
|
||||
|
||||
// Keep uncompressed entries (modifies history)
|
||||
// Keep uncompressed entries (modifies history and returns removed items)
|
||||
let newHistory = _.remove(history, entry => {
|
||||
let date = moment(entry.date);
|
||||
return date.isSame(cutOff) || date.isAfter(cutOff);
|
||||
|
|
|
|||
|
|
@ -26,10 +26,7 @@ if (gcm) {
|
|||
}
|
||||
|
||||
module.exports = function sendNotification (user, title, message, timeToLive = 15) {
|
||||
// TODO need investigation:
|
||||
// https://github.com/HabitRPG/habitrpg/issues/5252
|
||||
|
||||
if (!user) throw new Error('User is required.');
|
||||
if (!user) return;
|
||||
|
||||
_.each(user.pushDevices, pushDevice => {
|
||||
switch (pushDevice.type) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
model as User,
|
||||
} from '../../models/user';
|
||||
|
||||
// TODO how to translate the strings here since getUserLanguage hasn't run yet?
|
||||
// Strins won't be translated here because getUserLanguage has not run yet
|
||||
|
||||
// Authenticate a request through the x-api-user and x-api key header
|
||||
// If optional is true, don't error on missing authentication
|
||||
|
|
|
|||
|
|
@ -123,12 +123,12 @@ module.exports = function cronMiddleware (req, res, next) {
|
|||
$lt: moment(now).subtract(user.isSubscribed() ? 90 : 30, 'days').toDate(),
|
||||
},
|
||||
'challenge.id': {$exists: false},
|
||||
}).exec(); // TODO wait before returning?
|
||||
}).exec();
|
||||
|
||||
let ranCron = user.isModified();
|
||||
let quest = common.content.quests[user.party.quest.key];
|
||||
|
||||
// if (ranCron) res.locals.wasModified = true; // TODO remove?
|
||||
// if (ranCron) res.locals.wasModified = true; // TODO remove after v2 is retired
|
||||
if (!ranCron) return next();
|
||||
|
||||
// Group.tavernBoss(user, progress);
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ module.exports = function attachMiddlewares (app, server) {
|
|||
app.use(forceSSL);
|
||||
app.use(forceHabitica);
|
||||
|
||||
// TODO if we don't manage to move the client off $resource the limit for bodyParser.json must be increased to 1mb from 100kb (default)
|
||||
app.use(bodyParser.urlencoded({
|
||||
extended: true, // Uses 'qs' library as old connect middleware
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// TODO tests?
|
||||
// TODO test this middleware
|
||||
module.exports = function setupBodyMiddleware (req, res, next) {
|
||||
req.body = req.body || {};
|
||||
next();
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ v2app.use(responseHandler);
|
|||
|
||||
// Custom Directives
|
||||
v2app.use('/', require('../../routes/api-v2/auth'));
|
||||
v2app.use('/', require('../../routes/api-v2/coupon')); // TODO REMOVE - ONLY v3
|
||||
v2app.use('/', require('../../routes/api-v2/unsubscription')); // TODO REMOVE - ONLY v3
|
||||
// v2app.use('/', require('../../routes/api-v2/coupon')); // TODO REMOVE - ONLY v3
|
||||
// v2app.use('/', require('../../routes/api-v2/unsubscription')); // TODO REMOVE - ONLY v3
|
||||
|
||||
require('../../routes/api-v2/swagger')(swagger, v2app);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@ var limiter = require('connect-ratelimit');
|
|||
var IS_PROD = nconf.get('NODE_ENV') === 'production';
|
||||
|
||||
// TODO since Habitica runs on many different servers this module is pretty useless
|
||||
// as it will only block requests that go to the same server
|
||||
// as it will only block requests that go to the same server but anyway we should probably have a rate limiter in place
|
||||
|
||||
module.exports = function(app) {
|
||||
// TODO review later
|
||||
// disable the rate limiter middleware
|
||||
if (/*!IS_PROD || */true) return;
|
||||
app.use(limiter({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// TODO do we need this module?
|
||||
// TODO do we need this module anymore in v3? No
|
||||
|
||||
module.exports.siteVersion = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ schema.methods.syncToUser = async function syncChallengeToUser (user) {
|
|||
user.tasksOrder[`${chalTask.type}s`].push(matchingTask._id);
|
||||
} else {
|
||||
_.merge(matchingTask, _syncableAttrs(chalTask));
|
||||
// Make sure the task is in user.tasksOrder TODO necessary?
|
||||
// Make sure the task is in user.tasksOrder
|
||||
let orderList = user.tasksOrder[`${chalTask.type}s`];
|
||||
if (orderList.indexOf(matchingTask._id) === -1 && (matchingTask.type !== 'todo' || !matchingTask.completed)) orderList.push(matchingTask._id);
|
||||
}
|
||||
|
|
@ -155,7 +155,7 @@ schema.methods.addTasks = async function challengeAddTasks (tasks) {
|
|||
let membersIds = await _fetchMembersIds(challenge._id);
|
||||
|
||||
// Sync each user sequentially
|
||||
// TODO are we sure it's the best solution?
|
||||
// TODO are we sure it's the best solution? Use cwait
|
||||
// use bulk ops? http://stackoverflow.com/questions/16726330/mongoose-mongodb-batch-insert
|
||||
for (let memberId of membersIds) {
|
||||
let updateTasksOrderQ = {$push: {}};
|
||||
|
|
|
|||
|
|
@ -149,25 +149,35 @@ schema.statics.getGroups = async function getGroups (options = {}) {
|
|||
queries.push(this.getGroup({user, groupId: 'party', fields: groupFields, populateLeader}));
|
||||
break;
|
||||
}
|
||||
case 'guilds': {
|
||||
let userGuildsQuery = this.find({
|
||||
type: 'guild',
|
||||
_id: {$in: user.guilds},
|
||||
}).select(groupFields);
|
||||
if (populateLeader === true) userGuildsQuery.populate('leader', nameFields);
|
||||
userGuildsQuery.sort(sort).exec();
|
||||
queries.push(userGuildsQuery);
|
||||
break;
|
||||
}
|
||||
case 'privateGuilds': {
|
||||
let privateGroupQuery = this.find({
|
||||
let privateGuildsQuery = this.find({
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
_id: {$in: user.guilds},
|
||||
}).select(groupFields);
|
||||
if (populateLeader === true) privateGroupQuery.populate('leader', nameFields);
|
||||
privateGroupQuery.sort(sort).exec();
|
||||
queries.push(privateGroupQuery);
|
||||
if (populateLeader === true) privateGuildsQuery.populate('leader', nameFields);
|
||||
privateGuildsQuery.sort(sort).exec();
|
||||
queries.push(privateGuildsQuery);
|
||||
break;
|
||||
}
|
||||
case 'publicGuilds': {
|
||||
let publicGroupQuery = this.find({
|
||||
let publicGuildsQuery = this.find({
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
}).select(groupFields);
|
||||
if (populateLeader === true) publicGroupQuery.populate('leader', nameFields);
|
||||
publicGroupQuery.sort(sort).exec();
|
||||
queries.push(publicGroupQuery); // TODO use lean?
|
||||
if (populateLeader === true) publicGuildsQuery.populate('leader', nameFields);
|
||||
publicGuildsQuery.sort(sort).exec();
|
||||
queries.push(publicGuildsQuery); // TODO use lean?
|
||||
break;
|
||||
}
|
||||
case 'tavern': {
|
||||
|
|
@ -410,7 +420,7 @@ schema.methods.finishQuest = function finishQuest (quest) {
|
|||
let updates = {$inc: {}, $set: {}};
|
||||
|
||||
updates.$inc[`achievements.quests.${questK}`] = 1;
|
||||
updates.$inc['stats.gp'] = Number(quest.drop.gp); // TODO are this castings necessary?
|
||||
updates.$inc['stats.gp'] = Number(quest.drop.gp);
|
||||
updates.$inc['stats.exp'] = Number(quest.drop.exp);
|
||||
updates.$inc._v = 1;
|
||||
|
||||
|
|
@ -520,7 +530,8 @@ schema.statics.bossQuest = async function bossQuest (user, progress) {
|
|||
}, {multi: true}).exec();
|
||||
// Apply changes the currently cronning user locally so we don't have to reload it to get the updated state
|
||||
// TODO how to mark not modified? https://github.com/Automattic/mongoose/pull/1167
|
||||
// must be notModified or otherwise could overwrite future changes
|
||||
// must be notModified or otherwise could overwrite future changes: if the user is saved it'll save
|
||||
// the modified user.stats.hp but that must not happen as the hp value has already been updated by the User.update above
|
||||
// if (down) user.stats.hp += down;
|
||||
|
||||
// Boss slain, finish quest
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export let TaskSchema = new Schema({
|
|||
|
||||
challenge: {
|
||||
id: {type: String, ref: 'Challenge', validate: [validator.isUUID, 'Invalid uuid.']}, // When set (and userId not set) it's the original task
|
||||
taskId: {type: String, ref: 'Task', validate: [validator.isUUID, 'Invalid uuid.']}, // When not set but challenge.id defined it's the original task TODO unique index?
|
||||
taskId: {type: String, ref: 'Task', validate: [validator.isUUID, 'Invalid uuid.']}, // When not set but challenge.id defined it's the original task
|
||||
broken: {type: String, enum: ['CHALLENGE_DELETED', 'TASK_DELETED', 'UNSUBSCRIBED', 'CHALLENGE_CLOSED']},
|
||||
winner: String, // user.profile.name of the winner
|
||||
},
|
||||
|
|
@ -149,7 +149,7 @@ export let Task = mongoose.model('Task', TaskSchema);
|
|||
|
||||
// habits and dailies shared fields
|
||||
let habitDailySchema = () => {
|
||||
return {history: Array}; // [{date:Date, value:Number}], // this causes major performance problems TODO revisit
|
||||
return {history: Array}; // [{date:Date, value:Number}], // this causes major performance problems
|
||||
};
|
||||
|
||||
// dailys and todos shared fields
|
||||
|
|
@ -197,7 +197,7 @@ export let daily = Task.discriminator('daily', DailySchema);
|
|||
|
||||
export let TodoSchema = new Schema(_.defaults({
|
||||
dateCompleted: Date,
|
||||
// TODO we're getting parse errors, people have stored as "today" and "3/13". Need to run a migration & put this back to type: Date
|
||||
// TODO we're getting parse errors, people have stored as "today" and "3/13". Need to run a migration & put this back to type: Date see http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript
|
||||
date: String, // due date for todos
|
||||
}, dailyTodoSchema()), subDiscriminatorOptions);
|
||||
export let todo = Task.discriminator('todo', TodoSchema);
|
||||
|
|
|
|||
|
|
@ -30,13 +30,10 @@ export let schema = new Schema({
|
|||
local: {
|
||||
email: {
|
||||
type: String,
|
||||
trim: true,
|
||||
lowercase: true,
|
||||
validate: [validator.isEmail, shared.i18n.t('invalidEmail')], // TODO translate error messages here, use preferences.language?
|
||||
validate: [validator.isEmail, shared.i18n.t('invalidEmail')],
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
// Store a lowercase version of username to check for duplicates
|
||||
lowerCaseUsername: String,
|
||||
|
|
@ -529,14 +526,14 @@ export let schema = new Schema({
|
|||
|
||||
schema.plugin(baseModel, {
|
||||
// TODO revisit a lot of things are missing. Given how many attributes we do have here we should white-list the ones that can be updated
|
||||
// TODO this is a only used for creating an user, on update we use a whitelist
|
||||
// This is not really used as updating uses a whitelist and creating only accepts specific params (password, email, username, ...)
|
||||
noSet: ['_id', 'apiToken', 'auth.blocked', 'auth.timestamps', 'lastCron', 'auth.local.hashed_password',
|
||||
'auth.local.salt', 'tasksOrder', 'tags', 'stats', 'challenges', 'guilds', 'party._id', 'party.quest',
|
||||
'invitations', 'balance', 'backer', 'contributor'],
|
||||
private: ['auth.local.hashed_password', 'auth.local.salt'],
|
||||
toJSONTransform: function userToJSON (plainObj, originalDoc) {
|
||||
// plainObj.filters = {}; TODO Not saved
|
||||
plainObj._tmp = originalDoc._tmp; // be sure to send down drop notifs TODO how to test?
|
||||
// plainObj.filters = {}; TODO Not saved, remove?
|
||||
plainObj._tmp = originalDoc._tmp; // be sure to send down drop notifs
|
||||
|
||||
return plainObj;
|
||||
},
|
||||
|
|
@ -593,7 +590,7 @@ function _populateDefaultTasks (user, taskTypes) {
|
|||
return newTask.save();
|
||||
});
|
||||
|
||||
tasksToCreate.push(...tasksOfType); // TODO find better way since this creates each task individually
|
||||
tasksToCreate.push(...tasksOfType);
|
||||
});
|
||||
|
||||
return Q.all(tasksToCreate)
|
||||
|
|
|
|||
Loading…
Reference in a new issue