diff --git a/website/server/controllers/api-v2/auth.js b/website/server/controllers/api-v2/auth.js
index df8adf7a52..cfa5c40a0d 100644
--- a/website/server/controllers/api-v2/auth.js
+++ b/website/server/controllers/api-v2/auth.js
@@ -134,7 +134,6 @@ api.registerUser = function(req, res, next) {
user.save(function(err, savedUser){
if (err) return cb(err);
// Clean previous email preferences
- // TODO when emails added to EmailUnsubcription they should use lowercase version
EmailUnsubscription.remove({email: savedUser.auth.local.email}, function(){
utils.txnEmail(savedUser, 'welcome');
});
diff --git a/website/server/controllers/api-v2/challenges.js b/website/server/controllers/api-v2/challenges.js
index 93beccbd48..c5d716d88e 100644
--- a/website/server/controllers/api-v2/challenges.js
+++ b/website/server/controllers/api-v2/challenges.js
@@ -55,7 +55,7 @@ api.list = async function(req, res, next) {
return obj;
});
- // TODO Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
+ // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
await Bluebird.all(resChals.map((chal, index) => {
return Bluebird.all([
User.findById(chal.leader).select(nameFields).exec(),
@@ -268,8 +268,7 @@ api.update = function(req, res, next){
async.forEachOf(newTasksObj, function(newTask, taskId, cb2){
// some properties can't be changed
newTask = Tasks.Task.sanitize(newTask);
- // TODO 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
+ // we have to convert task to an object because otherwise things don't get merged correctly. Bad for performances?
_.assign(updatedTasks[taskId], shared.ops.updateTask(updatedTasks[taskId].toObject(), {body: newTask}));
_before.chal.updateTask(updatedTasks[taskId]).then(cb2).catch(cb2);
}, cb1);
diff --git a/website/server/controllers/api-v2/user.js b/website/server/controllers/api-v2/user.js
index c79d6e7dec..daaa272491 100644
--- a/website/server/controllers/api-v2/user.js
+++ b/website/server/controllers/api-v2/user.js
@@ -429,7 +429,7 @@ api.delete = function(req, res, next) {
return res.status(400).json({err:"You have an active subscription, cancel your plan before deleting your account."});
}
- let types = ['party', 'publicGuilds', 'privateGuilds'];
+ let types = ['party', 'guilds'];
let groupFields = basicGroupFields.concat(' leader memberCount');
Group.getGroups({user, types, groupFields})
diff --git a/website/server/controllers/api-v3/email.js b/website/server/controllers/api-v3/email.js
index f8e5e4effe..675518648e 100644
--- a/website/server/controllers/api-v3/email.js
+++ b/website/server/controllers/api-v3/email.js
@@ -44,9 +44,9 @@ api.unsubscribe = {
res.send(`
${res.t('unsubscribedSuccessfully')}
${res.t('unsubscribedTextUsers')}`);
} else {
- let unsubscribedEmail = await EmailUnsubscription.findOne({email: data.email});
+ let unsubscribedEmail = await EmailUnsubscription.findOne({email: data.email.toLowerCase()});
let okResponse = `${res.t('unsubscribedSuccessfully')}
${res.t('unsubscribedTextOthers')}`;
- if (!unsubscribedEmail) await EmailUnsubscription.create({email: data.email});
+ if (!unsubscribedEmail) await EmailUnsubscription.create({email: data.email.toLowerCase()});
res.send(okResponse);
}
},
diff --git a/website/server/controllers/api-v3/quests.js b/website/server/controllers/api-v3/quests.js
index 3443f9d67d..832e90915a 100644
--- a/website/server/controllers/api-v3/quests.js
+++ b/website/server/controllers/api-v3/quests.js
@@ -383,7 +383,6 @@ api.abortQuest = {
'party._id': groupId,
}, {
$set: {'party.quest': Group.cleanQuestProgress()},
- $inc: {_v: 1}, // TODO update middleware
}, {multi: true}).exec();
let questLeaderUpdate = User.update({
diff --git a/website/server/controllers/api-v3/tasks.js b/website/server/controllers/api-v3/tasks.js
index 0abe8f051b..3b118bfe3c 100644
--- a/website/server/controllers/api-v3/tasks.js
+++ b/website/server/controllers/api-v3/tasks.js
@@ -323,7 +323,7 @@ function _generateWebhookTaskData (task, direction, delta, stats, user) {
let extendedStats = _.extend(stats, {
toNextLevel: common.tnl(user.stats.lvl),
maxHealth: common.maxHealth,
- maxMP: user._statsComputed.maxMP, // TODO refactor as method not getter
+ maxMP: common.statsComputed(user).maxMP,
});
let userData = {
@@ -428,7 +428,6 @@ api.scoreTask = {
};
// completed todos cannot be moved, they'll be returned ordered by date of completion
-// TODO support challenges?
/**
* @api {post} /api/v3/tasks/:taskId/move/to/:position Move a task to a new position
* @apiVersion 3.0.0
diff --git a/website/server/controllers/api-v3/user.js b/website/server/controllers/api-v3/user.js
index 1440e009f9..061541e954 100644
--- a/website/server/controllers/api-v3/user.js
+++ b/website/server/controllers/api-v3/user.js
@@ -36,10 +36,10 @@ api.getUser = {
// Remove apiToken from response TODO make it private at the user level? returned in signup/login
delete user.apiToken;
- // TODO move to model (maybe virtuals, maybe in toJSON)
+ // TODO move to model? (maybe virtuals, maybe in toJSON)
user.stats.toNextLevel = common.tnl(user.stats.lvl);
user.stats.maxHealth = common.maxHealth;
- user.stats.maxMP = res.locals.user._statsComputed.maxMP;
+ user.stats.maxMP = common.statsComputed(user).maxMP;
return res.respond(200, user);
},
@@ -210,7 +210,7 @@ api.deleteUser = {
throw new NotAuthorized(res.t('cannotDeleteActiveAccount'));
}
- let types = ['party', 'publicGuilds', 'privateGuilds'];
+ let types = ['party', 'guilds'];
let groupFields = basicGroupFields.concat(' leader memberCount');
let groupsUserIsMemberOf = await Group.getGroups({user, types, groupFields});
diff --git a/website/server/controllers/top-level/pages.js b/website/server/controllers/top-level/pages.js
index 4b0ce11ee2..22c65015f7 100644
--- a/website/server/controllers/top-level/pages.js
+++ b/website/server/controllers/top-level/pages.js
@@ -27,8 +27,7 @@ api.getFrontPage = {
},
};
-// TODO remove api static page
-let staticPages = ['front', 'privacy', 'terms', 'api', 'features',
+let staticPages = ['front', 'privacy', 'terms', 'api-v2', 'features',
'videos', 'contact', 'plans', 'new-stuff', 'community-guidelines',
'old-news', 'press-kit', 'faq', 'overview', 'apps',
'clear-browser-data', 'merch'];
diff --git a/website/server/libs/api-v3/baseModel.js b/website/server/libs/api-v3/baseModel.js
index e0b2101393..c736d613ea 100644
--- a/website/server/libs/api-v3/baseModel.js
+++ b/website/server/libs/api-v3/baseModel.js
@@ -32,6 +32,10 @@ module.exports = function baseModel (schema, options = {}) {
if (!this.isNew) this.updatedAt = Date.now();
next();
});
+
+ schema.pre('update', function preUpdateModel () {
+ this.update({}, { $set: { updatedAt: new Date() } });
+ });
}
let noSetFields = ['createdAt', 'updatedAt'];
diff --git a/website/server/libs/api-v3/cron.js b/website/server/libs/api-v3/cron.js
index 1fd9466786..e4a96ee07f 100644
--- a/website/server/libs/api-v3/cron.js
+++ b/website/server/libs/api-v3/cron.js
@@ -24,8 +24,8 @@ function grantEndOfTheMonthPerks (user, now) {
plan.dateUpdated = now;
// For every month, inc their "consecutive months" counter. Give perks based on consecutive blocks
// If they already got perks for those blocks (eg, 6mo subscription, subscription gifts, etc) - then dec the offset until it hits 0
- // TODO use month diff instead of ++ / --?
- _.defaults(plan.consecutive, {count: 0, offset: 0, trinkets: 0, gemCapExtra: 0}); // FIXME see https://github.com/HabitRPG/habitrpg/issues/4317
+ // TODO use month diff instead of ++ / --? see https://github.com/HabitRPG/habitrpg/issues/4317
+ _.defaults(plan.consecutive, {count: 0, offset: 0, trinkets: 0, gemCapExtra: 0});
plan.consecutive.count++;
diff --git a/website/server/libs/api-v3/logger.js b/website/server/libs/api-v3/logger.js
index a399cb6fbd..69e35abf97 100644
--- a/website/server/libs/api-v3/logger.js
+++ b/website/server/libs/api-v3/logger.js
@@ -2,7 +2,6 @@
import winston from 'winston';
import nconf from 'nconf';
import _ from 'lodash';
-import Bluebird from 'bluebird';
const IS_PROD = nconf.get('IS_PROD');
const IS_TEST = nconf.get('IS_TEST');
@@ -11,8 +10,6 @@ const ENABLE_CONSOLE_LOGS_IN_PROD = nconf.get('ENABLE_CONSOLE_LOGS_IN_PROD') ===
const logger = new winston.Logger();
if (IS_PROD) {
- // TODO production logging, use loggly and new relic too
-
if (ENABLE_CONSOLE_LOGS_IN_PROD === 'true') {
logger.add(winston.transports.Console, {
colorize: true,
@@ -54,15 +51,6 @@ let loggerInterface = {
},
};
-// Disable warnings for missed returns in Bluebird.
-// See https://github.com/petkaantonov/bluebird/issues/903
-Bluebird.config({
- // Enables all warnings except forgotten return statements.
- warnings: {
- wForgottenReturn: false,
- },
-});
-
// Logs unhandled promises errors
// when no catch is attached to a promise a unhandledRejection event will be triggered
process.on('unhandledRejection', function handlePromiseRejection (reason) {
diff --git a/website/server/middlewares/api-v3/auth.js b/website/server/middlewares/api-v3/auth.js
index 9effe37eae..2fbe437068 100644
--- a/website/server/middlewares/api-v3/auth.js
+++ b/website/server/middlewares/api-v3/auth.js
@@ -29,7 +29,7 @@ export function authWithHeaders (optional = false) {
if (user.auth.blocked) throw new NotAuthorized(res.t('accountSuspended', {userId: user._id}));
res.locals.user = user;
- // TODO use either session/cookie or headers, not both
+
req.session.userId = user._id;
return next();
})
diff --git a/website/server/middlewares/api-v3/cron.js b/website/server/middlewares/api-v3/cron.js
index a7f2801766..780f400e8f 100644
--- a/website/server/middlewares/api-v3/cron.js
+++ b/website/server/middlewares/api-v3/cron.js
@@ -128,7 +128,7 @@ module.exports = function cronMiddleware (req, res, next) {
let ranCron = user.isModified();
let quest = common.content.quests[user.party.quest.key];
- // if (ranCron) res.locals.wasModified = true; // TODO remove after v2 is retired
+ if (ranCron) res.locals.wasModified = true; // TODO remove after v2 is retired
if (!ranCron) return next();
// Group.tavernBoss(user, progress);
diff --git a/website/server/middlewares/api-v3/index.js b/website/server/middlewares/api-v3/index.js
index 4b526c10e1..6ccac2f844 100644
--- a/website/server/middlewares/api-v3/index.js
+++ b/website/server/middlewares/api-v3/index.js
@@ -61,7 +61,7 @@ module.exports = function attachMiddlewares (app, server) {
app.use(cookieSession({
name: 'connect:sess', // Used to keep backward compatibility with Express 3 cookies
secret: SESSION_SECRET,
- httpOnly: false, // TODO this should be true for security, what about https only?
+ httpOnly: false, // TODO this should be true for security, what about https only (secure) ?
maxAge: TWO_WEEKS,
}));
diff --git a/website/server/models/challenge.js b/website/server/models/challenge.js
index be61b0f00d..cd99fda995 100644
--- a/website/server/models/challenge.js
+++ b/website/server/models/challenge.js
@@ -74,7 +74,7 @@ schema.methods.canView = function canViewChallenge (user, group) {
function _syncableAttrs (task) {
let t = task.toObject(); // lodash doesn't seem to like _.omit on Document
// only sync/compare important attrs
- let omitAttrs = ['_id', 'userId', 'challenge', 'history', 'tags', 'completed', 'streak', 'notes']; // TODO what to do with updatedAt?
+ let omitAttrs = ['_id', 'userId', 'challenge', 'history', 'tags', 'completed', 'streak', 'notes', 'updatedAt'];
if (t.type !== 'reward') omitAttrs.push('value');
return _.omit(t, omitAttrs);
}
@@ -222,7 +222,7 @@ schema.methods.removeTask = async function challengeRemoveTask (task) {
'challenge.id': challenge.id,
'challenge.taskId': task._id,
}, {
- $set: {'challenge.broken': 'TASK_DELETED'}, // TODO what about updatedAt?
+ $set: {'challenge.broken': 'TASK_DELETED'},
}, {multi: true}).exec();
};
@@ -238,7 +238,7 @@ schema.methods.unlinkTasks = async function challengeUnlinkTasks (user, keep) {
if (keep === 'keep-all') {
await Tasks.Task.update(findQuery, {
- $set: {challenge: {}}, // TODO what about updatedAt?
+ $set: {challenge: {}},
}, {multi: true}).exec();
await user.save();
@@ -259,7 +259,7 @@ schema.methods.unlinkTasks = async function challengeUnlinkTasks (user, keep) {
};
// TODO everything here should be moved to a worker
-// actually even for a worker it's probably just too big and will kill mongo
+// actually even for a worker it's probably just too big and will kill mongo, figure out something else
schema.methods.closeChal = async function closeChal (broken = {}) {
let challenge = this;
diff --git a/website/server/models/group.js b/website/server/models/group.js
index aec5824fef..347079c96a 100644
--- a/website/server/models/group.js
+++ b/website/server/models/group.js
@@ -33,7 +33,6 @@ export let schema = new Schema({
leader: {type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.'], required: true},
type: {type: String, enum: ['guild', 'party'], required: true},
privacy: {type: String, enum: ['private', 'public'], default: 'private', required: true},
- // _v: {type: Number,'default': 0}, // TODO ?
chat: Array,
/*
# [{
@@ -94,7 +93,6 @@ schema.statics.sanitizeUpdate = function sanitizeUpdate (updateObj) {
// Basic fields to fetch for populating a group info
export let basicFields = 'name type privacy';
-// TODO test
schema.pre('remove', true, async function preRemoveGroup (next, done) {
next();
try {
@@ -179,14 +177,16 @@ schema.statics.getGroups = async function getGroups (options = {}) {
queries.push(privateGuildsQuery);
break;
}
+ // NOTE: when returning publicGuilds we use `.lean()` so all mongoose methods won't be available.
+ // Docs are going to be plain javascript objects
case 'publicGuilds': {
let publicGuildsQuery = this.find({
type: 'guild',
privacy: 'public',
}).select(groupFields);
if (populateLeader === true) publicGuildsQuery.populate('leader', nameFields);
- publicGuildsQuery.sort(sort).exec();
- queries.push(publicGuildsQuery); // TODO use lean?
+ publicGuildsQuery.sort(sort).lean().exec();
+ queries.push(publicGuildsQuery);
break;
}
case 'tavern': {
diff --git a/website/server/models/task.js b/website/server/models/task.js
index 1fbe351e18..0459a74580 100644
--- a/website/server/models/task.js
+++ b/website/server/models/task.js
@@ -142,7 +142,6 @@ TaskSchema.statics.fromJSONV2 = function fromJSONV2 (taskObj) {
return taskObj;
};
-
// END of API v2 methods
export let Task = mongoose.model('Task', TaskSchema);
diff --git a/website/server/models/user.js b/website/server/models/user.js
index 9cce45fc55..eda6760d9f 100644
--- a/website/server/models/user.js
+++ b/website/server/models/user.js
@@ -525,11 +525,8 @@ 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
- // 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'],
+ // noSet is not used as updating uses a whitelist and creating only accepts specific params (password, email, username, ...)
+ noSet: [],
private: ['auth.local.hashed_password', 'auth.local.salt'],
toJSONTransform: function userToJSON (plainObj, originalDoc) {
plainObj.id = plainObj._id;
@@ -638,7 +635,6 @@ function _setProfileName (user) {
schema.pre('save', true, function preSaveUser (next, done) {
next();
- // TODO remove all unnecessary checks
if (_.isNaN(this.preferences.dayStart) || this.preferences.dayStart < 0 || this.preferences.dayStart > 23) {
this.preferences.dayStart = 0;
}
@@ -697,7 +693,10 @@ schema.pre('save', true, function preSaveUser (next, done) {
}
});
-// TODO unit test this?
+schema.pre('update', function preUpdateUser () {
+ this.update({}, {$inc: {_v: 1}});
+});
+
schema.methods.isSubscribed = function isSubscribed () {
return !!this.purchased.plan.customerId; // eslint-disable-line no-implicit-coercion
};
diff --git a/website/views/static/api.jade b/website/views/static/api-v2.jade
similarity index 100%
rename from website/views/static/api.jade
rename to website/views/static/api-v2.jade