mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
v3: fix cron middleware, remove unused code from shared
This commit is contained in:
@@ -1,358 +0,0 @@
|
|||||||
import moment from 'moment';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import {
|
|
||||||
daysSince,
|
|
||||||
shouldDo,
|
|
||||||
} from '../cron';
|
|
||||||
import {
|
|
||||||
capByLevel,
|
|
||||||
toNextLevel,
|
|
||||||
} from '../statHelpers';
|
|
||||||
/*
|
|
||||||
------------------------------------------------------
|
|
||||||
Cron
|
|
||||||
------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
At end of day, add value to all incomplete Daily & Todo tasks (further incentive)
|
|
||||||
For incomplete Dailys, deduct experience
|
|
||||||
Make sure to run this function once in a while as server will not take care of overnight calculations.
|
|
||||||
And you have to run it every time client connects.
|
|
||||||
{user}
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function(user, options) {
|
|
||||||
var _progress, analyticsData, base, base1, base2, base3, base4, clearBuffs, dailyChecked, dailyDueUnchecked, daysMissed, expTally, lvl, lvlDiv2, multiDaysCountAsOneDay, now, perfect, plan, progress, ref, ref1, ref2, ref3, todoTally, timezoneOffsetFromUserPrefs, timezoneOffsetFromBrowser, timezoneOffsetAtLastCron;
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
now = +options.now || +(new Date);
|
|
||||||
|
|
||||||
// If the user's timezone has changed (due to travel or daylight savings),
|
|
||||||
// cron can be triggered twice in one day, so we check for that and use
|
|
||||||
// both timezones to work out if cron should run.
|
|
||||||
// CDS = Custom Day Start time.
|
|
||||||
timezoneOffsetFromUserPrefs = user.preferences.timezoneOffset || 0;
|
|
||||||
timezoneOffsetAtLastCron = (_.isFinite(user.preferences.timezoneOffsetAtLastCron)) ? user.preferences.timezoneOffsetAtLastCron : timezoneOffsetFromUserPrefs;
|
|
||||||
timezoneOffsetFromBrowser = (_.isFinite(+options.timezoneOffset)) ? +options.timezoneOffset : timezoneOffsetFromUserPrefs;
|
|
||||||
// NB: All timezone offsets can be 0, so can't use `... || ...` to apply non-zero defaults
|
|
||||||
|
|
||||||
if (timezoneOffsetFromBrowser !== timezoneOffsetFromUserPrefs) {
|
|
||||||
// The user's browser has just told Habitica that the user's timezone has
|
|
||||||
// changed so store and use the new zone.
|
|
||||||
user.preferences.timezoneOffset = timezoneOffsetFromBrowser;
|
|
||||||
timezoneOffsetFromUserPrefs = timezoneOffsetFromBrowser;
|
|
||||||
}
|
|
||||||
|
|
||||||
// How many days have we missed using the user's current timezone:
|
|
||||||
daysMissed = daysSince(user.lastCron, _.defaults({
|
|
||||||
now: now
|
|
||||||
}, user.preferences));
|
|
||||||
|
|
||||||
if (timezoneOffsetAtLastCron != timezoneOffsetFromUserPrefs) {
|
|
||||||
// Since cron last ran, the user's timezone has changed.
|
|
||||||
// How many days have we missed using the old timezone:
|
|
||||||
let daysMissedNewZone = daysMissed;
|
|
||||||
let daysMissedOldZone = daysSince(user.lastCron, _.defaults({
|
|
||||||
now: now,
|
|
||||||
timezoneOffsetOverride: timezoneOffsetAtLastCron,
|
|
||||||
}, user.preferences));
|
|
||||||
|
|
||||||
if (timezoneOffsetAtLastCron < timezoneOffsetFromUserPrefs) {
|
|
||||||
// The timezone change was in the unsafe direction.
|
|
||||||
// E.g., timezone changes from UTC+1 (offset -60) to UTC+0 (offset 0).
|
|
||||||
// or timezone changes from UTC-4 (offset 240) to UTC-5 (offset 300).
|
|
||||||
// Local time changed from, for example, 03:00 to 02:00.
|
|
||||||
|
|
||||||
if (daysMissedOldZone > 0 && daysMissedNewZone > 0) {
|
|
||||||
// Both old and new timezones indicate that we SHOULD run cron, so
|
|
||||||
// it is safe to do so immediately.
|
|
||||||
daysMissed = Math.min(daysMissedOldZone, daysMissedNewZone);
|
|
||||||
// use minimum value to be nice to user
|
|
||||||
}
|
|
||||||
else if (daysMissedOldZone > 0) {
|
|
||||||
// The old timezone says that cron should run; the new timezone does not.
|
|
||||||
// This should be impossible for this direction of timezone change, but
|
|
||||||
// just in case I'm wrong...
|
|
||||||
console.log("zone has changed - old zone says run cron, NEW zone says no - stop cron now only -- SHOULD NOT HAVE GOT TO HERE", timezoneOffsetAtLastCron, timezoneOffsetFromUserPrefs, now); // used in production for confirming this never happens
|
|
||||||
}
|
|
||||||
else if (daysMissedNewZone > 0) {
|
|
||||||
// The old timezone says that cron should NOT run -- i.e., cron has
|
|
||||||
// already run today, from the old timezone's point of view.
|
|
||||||
// The new timezone says that cron SHOULD run, but this is almost
|
|
||||||
// certainly incorrect.
|
|
||||||
// This happens when cron occurred at a time soon after the CDS. When
|
|
||||||
// you reinterpret that time in the new timezone, it looks like it
|
|
||||||
// was before the CDS, because local time has stepped backwards.
|
|
||||||
// To fix this, rewrite the cron time to a time that the new
|
|
||||||
// timezone interprets as being in today.
|
|
||||||
|
|
||||||
daysMissed = 0; // prevent cron running now
|
|
||||||
let timezoneOffsetDiff = timezoneOffsetAtLastCron - timezoneOffsetFromUserPrefs;
|
|
||||||
// e.g., for dangerous zone change: 240 - 300 = -60 or -660 - -600 = -60
|
|
||||||
|
|
||||||
user.lastCron = moment(user.lastCron).subtract(timezoneOffsetDiff, 'minutes');
|
|
||||||
// NB: We don't change user.auth.timestamps.loggedin so that will still record the time that the previous cron actually ran.
|
|
||||||
// From now on we can ignore the old timezone:
|
|
||||||
user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Both old and new timezones indicate that cron should
|
|
||||||
// NOT run.
|
|
||||||
daysMissed = 0; // prevent cron running now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (timezoneOffsetAtLastCron > timezoneOffsetFromUserPrefs) {
|
|
||||||
daysMissed = daysMissedNewZone;
|
|
||||||
// TODO: Either confirm that there is nothing that could possibly go wrong here and remove the need for this else branch, or fix stuff. There are probably situations where the Dailies do not reset early enough for a user who was expecting the zone change and wants to use all their Dailies immediately in the new zone; if so, we should provide an option for easy reset of Dailies (can't be automatic because there will be other situations where the user was not prepared).
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(daysMissed > 0)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
user.auth.timestamps.loggedin = new Date();
|
|
||||||
user.lastCron = now;
|
|
||||||
user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs;
|
|
||||||
if (user.items.lastDrop.count > 0) {
|
|
||||||
user.items.lastDrop.count = 0;
|
|
||||||
}
|
|
||||||
perfect = true;
|
|
||||||
clearBuffs = {
|
|
||||||
str: 0,
|
|
||||||
int: 0,
|
|
||||||
per: 0,
|
|
||||||
con: 0,
|
|
||||||
stealth: 0,
|
|
||||||
streaks: false
|
|
||||||
};
|
|
||||||
plan = (ref = user.purchased) != null ? ref.plan : void 0;
|
|
||||||
if (plan != null ? plan.customerId : void 0) {
|
|
||||||
if (typeof plan.dateUpdated === "undefined") {
|
|
||||||
// partial compensation for bug in subscription creation - https://github.com/HabitRPG/habitrpg/issues/6682
|
|
||||||
plan.dateUpdated = new Date();
|
|
||||||
}
|
|
||||||
if (moment(plan.dateUpdated).format('MMYYYY') !== moment().format('MMYYYY')) {
|
|
||||||
plan.gemsBought = 0;
|
|
||||||
plan.dateUpdated = new Date();
|
|
||||||
_.defaults(plan.consecutive, {
|
|
||||||
count: 0,
|
|
||||||
offset: 0,
|
|
||||||
trinkets: 0,
|
|
||||||
gemCapExtra: 0
|
|
||||||
});
|
|
||||||
plan.consecutive.count++;
|
|
||||||
if (plan.consecutive.offset > 0) {
|
|
||||||
plan.consecutive.offset--;
|
|
||||||
} else if (plan.consecutive.count % 3 === 0) {
|
|
||||||
plan.consecutive.trinkets++;
|
|
||||||
plan.consecutive.gemCapExtra += 5;
|
|
||||||
if (plan.consecutive.gemCapExtra > 25) {
|
|
||||||
plan.consecutive.gemCapExtra = 25;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (plan.dateTerminated && moment(plan.dateTerminated).isBefore(+(new Date))) {
|
|
||||||
_.merge(plan, {
|
|
||||||
planId: null,
|
|
||||||
customerId: null,
|
|
||||||
paymentMethod: null
|
|
||||||
});
|
|
||||||
_.merge(plan.consecutive, {
|
|
||||||
count: 0,
|
|
||||||
offset: 0,
|
|
||||||
gemCapExtra: 0
|
|
||||||
});
|
|
||||||
if (typeof user.markModified === "function") {
|
|
||||||
user.markModified('purchased.plan');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (user.preferences.sleep === true) {
|
|
||||||
user.stats.buffs = clearBuffs;
|
|
||||||
user.dailys.forEach(function(daily) {
|
|
||||||
var completed, repeat, thatDay;
|
|
||||||
completed = daily.completed, repeat = daily.repeat;
|
|
||||||
thatDay = moment(now).subtract({
|
|
||||||
days: 1
|
|
||||||
});
|
|
||||||
if (shouldDo(thatDay.toDate(), daily, user.preferences) || completed) {
|
|
||||||
_.each(daily.checklist, (function(box) {
|
|
||||||
box.completed = false;
|
|
||||||
return true;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
return daily.completed = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
multiDaysCountAsOneDay = true;
|
|
||||||
todoTally = 0;
|
|
||||||
user.todos.forEach(function(task) {
|
|
||||||
var absVal, completed, delta, id;
|
|
||||||
if (!task) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
id = task.id, completed = task.completed;
|
|
||||||
delta = user.ops.score({
|
|
||||||
params: {
|
|
||||||
id: task.id,
|
|
||||||
direction: 'down'
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
times: multiDaysCountAsOneDay != null ? multiDaysCountAsOneDay : {
|
|
||||||
1: daysMissed
|
|
||||||
},
|
|
||||||
cron: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
absVal = completed ? Math.abs(task.value) : task.value;
|
|
||||||
return todoTally += absVal;
|
|
||||||
});
|
|
||||||
dailyChecked = 0;
|
|
||||||
dailyDueUnchecked = 0;
|
|
||||||
if ((base = user.party.quest.progress).down == null) {
|
|
||||||
base.down = 0;
|
|
||||||
}
|
|
||||||
user.dailys.forEach(function(task) {
|
|
||||||
var EvadeTask, completed, delta, fractionChecked, id, j, n, ref1, ref2, scheduleMisses, thatDay;
|
|
||||||
if (!task) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
id = task.id, completed = task.completed;
|
|
||||||
EvadeTask = 0;
|
|
||||||
scheduleMisses = daysMissed;
|
|
||||||
if (completed) {
|
|
||||||
dailyChecked += 1;
|
|
||||||
} else {
|
|
||||||
scheduleMisses = 0;
|
|
||||||
for (n = j = 0, ref1 = daysMissed; 0 <= ref1 ? j < ref1 : j > ref1; n = 0 <= ref1 ? ++j : --j) {
|
|
||||||
thatDay = moment(now).subtract({
|
|
||||||
days: n + 1
|
|
||||||
});
|
|
||||||
if (shouldDo(thatDay.toDate(), task, user.preferences)) {
|
|
||||||
scheduleMisses++;
|
|
||||||
if (user.stats.buffs.stealth) {
|
|
||||||
user.stats.buffs.stealth--;
|
|
||||||
EvadeTask++;
|
|
||||||
}
|
|
||||||
if (multiDaysCountAsOneDay) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (scheduleMisses > EvadeTask) {
|
|
||||||
perfect = false;
|
|
||||||
if (((ref2 = task.checklist) != null ? ref2.length : void 0) > 0) {
|
|
||||||
fractionChecked = _.reduce(task.checklist, (function(m, i) {
|
|
||||||
return m + (i.completed ? 1 : 0);
|
|
||||||
}), 0) / task.checklist.length;
|
|
||||||
dailyDueUnchecked += 1 - fractionChecked;
|
|
||||||
dailyChecked += fractionChecked;
|
|
||||||
} else {
|
|
||||||
dailyDueUnchecked += 1;
|
|
||||||
}
|
|
||||||
delta = user.ops.score({
|
|
||||||
params: {
|
|
||||||
id: task.id,
|
|
||||||
direction: 'down'
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
times: multiDaysCountAsOneDay != null ? multiDaysCountAsOneDay : {
|
|
||||||
1: scheduleMisses - EvadeTask
|
|
||||||
},
|
|
||||||
cron: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
user.party.quest.progress.down += delta * (task.priority < 1 ? task.priority : 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(task.history != null ? task.history : task.history = []).push({
|
|
||||||
date: +(new Date),
|
|
||||||
value: task.value
|
|
||||||
});
|
|
||||||
task.completed = false;
|
|
||||||
if (completed || (scheduleMisses > 0)) {
|
|
||||||
return _.each(task.checklist, (function(i) {
|
|
||||||
i.completed = false;
|
|
||||||
return true;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
user.habits.forEach(function(task) {
|
|
||||||
if (task.up === false || task.down === false) {
|
|
||||||
if (Math.abs(task.value) < 0.1) {
|
|
||||||
return task.value = 0;
|
|
||||||
} else {
|
|
||||||
return task.value = task.value / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
((base1 = (user.history != null ? user.history : user.history = {})).todos != null ? base1.todos : base1.todos = []).push({
|
|
||||||
date: now,
|
|
||||||
value: todoTally
|
|
||||||
});
|
|
||||||
expTally = user.stats.exp;
|
|
||||||
lvl = 0;
|
|
||||||
while (lvl < (user.stats.lvl - 1)) {
|
|
||||||
lvl++;
|
|
||||||
expTally += toNextLevel(lvl);
|
|
||||||
}
|
|
||||||
((base2 = user.history).exp != null ? base2.exp : base2.exp = []).push({
|
|
||||||
date: now,
|
|
||||||
value: expTally
|
|
||||||
});
|
|
||||||
if (!((ref1 = user.purchased) != null ? (ref2 = ref1.plan) != null ? ref2.customerId : void 0 : void 0)) {
|
|
||||||
user.fns.preenUserHistory();
|
|
||||||
if (typeof user.markModified === "function") {
|
|
||||||
user.markModified('history');
|
|
||||||
}
|
|
||||||
if (typeof user.markModified === "function") {
|
|
||||||
user.markModified('dailys');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
user.stats.buffs = perfect ? ((base3 = user.achievements).perfect != null ? base3.perfect : base3.perfect = 0, user.achievements.perfect++, lvlDiv2 = Math.ceil(capByLevel(user.stats.lvl) / 2), {
|
|
||||||
str: lvlDiv2,
|
|
||||||
int: lvlDiv2,
|
|
||||||
per: lvlDiv2,
|
|
||||||
con: lvlDiv2,
|
|
||||||
stealth: 0,
|
|
||||||
streaks: false
|
|
||||||
}) : clearBuffs;
|
|
||||||
if (dailyDueUnchecked === 0 && dailyChecked === 0) {
|
|
||||||
dailyChecked = 1;
|
|
||||||
}
|
|
||||||
user.stats.mp += _.max([10, .1 * user._statsComputed.maxMP]) * dailyChecked / (dailyDueUnchecked + dailyChecked);
|
|
||||||
if (user.stats.mp > user._statsComputed.maxMP) {
|
|
||||||
user.stats.mp = user._statsComputed.maxMP;
|
|
||||||
}
|
|
||||||
progress = user.party.quest.progress;
|
|
||||||
_progress = _.cloneDeep(progress);
|
|
||||||
_.merge(progress, {
|
|
||||||
down: 0,
|
|
||||||
up: 0
|
|
||||||
});
|
|
||||||
progress.collect = _.transform(progress.collect, (function(m, v, k) {
|
|
||||||
return m[k] = 0;
|
|
||||||
}));
|
|
||||||
if ((base4 = user.flags).cronCount == null) {
|
|
||||||
base4.cronCount = 0;
|
|
||||||
}
|
|
||||||
user.flags.cronCount++;
|
|
||||||
analyticsData = {
|
|
||||||
category: 'behavior',
|
|
||||||
gaLabel: 'Cron Count',
|
|
||||||
gaValue: user.flags.cronCount,
|
|
||||||
uuid: user._id,
|
|
||||||
user: user,
|
|
||||||
resting: user.preferences.sleep,
|
|
||||||
cronCount: user.flags.cronCount,
|
|
||||||
progressUp: _.min([_progress.up, 900]),
|
|
||||||
progressDown: _progress.down
|
|
||||||
};
|
|
||||||
if ((ref3 = options.analytics) != null) {
|
|
||||||
ref3.track('Cron', analyticsData);
|
|
||||||
}
|
|
||||||
return _progress;
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import dotGet from '../libs/dotGet';
|
import _ from 'lodash';
|
||||||
|
|
||||||
module.exports = function(user, path) {
|
// TODO remove completely, use _.get
|
||||||
return dotGet(user, path);
|
|
||||||
|
module.exports = function dotGet (user, path) {
|
||||||
|
return _.get(user, path);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import dotSet from '../libs/dotSet';
|
import _ from 'lodash';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This allows you to set object properties by dot-path. Eg, you can run pathSet('stats.hp',50,user) which is the same as
|
This allows you to set object properties by dot-path. Eg, you can run pathSet('stats.hp',50,user) which is the same as
|
||||||
user.stats.hp = 50. This is useful because in our habitrpg-shared functions we're returning changesets as {path:value},
|
user.stats.hp = 50. This is useful because in our habitrpg-shared functions we're returning changesets as {path:value},
|
||||||
so that different consumers can implement setters their own way. Derby needs model.set(path, value) for example, where
|
so that different consumers can implement setters their own way. Derby needs model.set(path, value) for example, where
|
||||||
Angular sets object properties directly - in which case, this function will be used.
|
Angular sets object properties directly - in which case, this function will be used.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = function(user, path, val) {
|
// TODO use directly _.set and remove this fn
|
||||||
return dotSet(user, path, val);
|
|
||||||
|
module.exports = function dotSet (user, path, val) {
|
||||||
|
return _.set(user, path, val);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import content from '../content/index';
|
|
||||||
import i18n from '../i18n';
|
|
||||||
|
|
||||||
module.exports = function(user, type) {
|
|
||||||
var item;
|
|
||||||
item = content.gear.flat[user.items.gear.equipped[type]];
|
|
||||||
if (!item) {
|
|
||||||
return content.gear.flat[type + "_base_0"];
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import getItem from './getItem';
|
|
||||||
import handleTwoHanded from './handleTwoHanded';
|
import handleTwoHanded from './handleTwoHanded';
|
||||||
import predictableRandom from './predictableRandom';
|
import predictableRandom from './predictableRandom';
|
||||||
import crit from './crit';
|
import crit from './crit';
|
||||||
@@ -8,13 +7,10 @@ import dotGet from './dotGet';
|
|||||||
import randomDrop from './randomDrop';
|
import randomDrop from './randomDrop';
|
||||||
import autoAllocate from './autoAllocate';
|
import autoAllocate from './autoAllocate';
|
||||||
import updateStats from './updateStats';
|
import updateStats from './updateStats';
|
||||||
import cron from './cron';
|
|
||||||
import preenUserHistory from './preenUserHistory';
|
|
||||||
import ultimateGear from './ultimateGear';
|
import ultimateGear from './ultimateGear';
|
||||||
import nullify from './nullify';
|
import nullify from './nullify';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getItem,
|
|
||||||
handleTwoHanded,
|
handleTwoHanded,
|
||||||
predictableRandom,
|
predictableRandom,
|
||||||
crit,
|
crit,
|
||||||
@@ -24,8 +20,6 @@ module.exports = {
|
|||||||
randomDrop,
|
randomDrop,
|
||||||
autoAllocate,
|
autoAllocate,
|
||||||
updateStats,
|
updateStats,
|
||||||
cron,
|
|
||||||
preenUserHistory,
|
|
||||||
ultimateGear,
|
ultimateGear,
|
||||||
nullify,
|
nullify,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
module.exports = function(user) {
|
// TODO remove once v2 is retired
|
||||||
|
|
||||||
|
module.exports = function nullify (user) {
|
||||||
user.ops = null;
|
user.ops = null;
|
||||||
user.fns = null;
|
user.fns = null;
|
||||||
return user = null;
|
user = null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import preenHistory from '../libs/preenHistory';
|
|
||||||
|
|
||||||
module.exports = function(user, minHistLen) {
|
|
||||||
if (minHistLen == null) {
|
|
||||||
minHistLen = 7;
|
|
||||||
}
|
|
||||||
_.each(user.habits.concat(user.dailys), function(task) {
|
|
||||||
var ref;
|
|
||||||
if (((ref = task.history) != null ? ref.length : void 0) > minHistLen) {
|
|
||||||
task.history = preenHistory(task.history);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
_.defaults(user.history, {
|
|
||||||
todos: [],
|
|
||||||
exp: []
|
|
||||||
});
|
|
||||||
if (user.history.exp.length > minHistLen) {
|
|
||||||
user.history.exp = preenHistory(user.history.exp);
|
|
||||||
}
|
|
||||||
if (user.history.todos.length > minHistLen) {
|
|
||||||
return user.history.todos = preenHistory(user.history.todos);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -58,21 +58,12 @@ api.updateStore = updateStore;
|
|||||||
import uuid from './libs/uuid';
|
import uuid from './libs/uuid';
|
||||||
api.uuid = uuid;
|
api.uuid = uuid;
|
||||||
|
|
||||||
import countExists from './libs/countExists';
|
|
||||||
api.countExists = countExists;
|
|
||||||
|
|
||||||
import taskDefaults from './libs/taskDefaults';
|
import taskDefaults from './libs/taskDefaults';
|
||||||
api.taskDefaults = taskDefaults;
|
api.taskDefaults = taskDefaults;
|
||||||
|
|
||||||
import percent from './libs/percent';
|
import percent from './libs/percent';
|
||||||
api.percent = percent;
|
api.percent = percent;
|
||||||
|
|
||||||
import removeWhitespace from './libs/removeWhitespace';
|
|
||||||
api.removeWhitespace = removeWhitespace;
|
|
||||||
|
|
||||||
import encodeiCalLink from './libs/encodeiCalLink';
|
|
||||||
api.encodeiCalLink = encodeiCalLink;
|
|
||||||
|
|
||||||
import gold from './libs/gold';
|
import gold from './libs/gold';
|
||||||
api.gold = gold;
|
api.gold = gold;
|
||||||
|
|
||||||
@@ -82,12 +73,6 @@ api.silver = silver;
|
|||||||
import taskClasses from './libs/taskClasses';
|
import taskClasses from './libs/taskClasses';
|
||||||
api.taskClasses = taskClasses;
|
api.taskClasses = taskClasses;
|
||||||
|
|
||||||
import friendlyTimestamp from './libs/friendlyTimestamp';
|
|
||||||
api.friendlyTimestamp = friendlyTimestamp;
|
|
||||||
|
|
||||||
import newChatMessages from './libs/newChatMessages';
|
|
||||||
api.newChatMessages = newChatMessages;
|
|
||||||
|
|
||||||
import noTags from './libs/noTags';
|
import noTags from './libs/noTags';
|
||||||
api.noTags = noTags;
|
api.noTags = noTags;
|
||||||
|
|
||||||
@@ -261,12 +246,11 @@ api.wrap = function wrapUser (user, main = true) {
|
|||||||
allocate: _.partial(importedOps.allocate, user),
|
allocate: _.partial(importedOps.allocate, user),
|
||||||
readCard: _.partial(importedOps.readCard, user),
|
readCard: _.partial(importedOps.readCard, user),
|
||||||
openMysteryItem: _.partial(importedOps.openMysteryItem, user),
|
openMysteryItem: _.partial(importedOps.openMysteryItem, user),
|
||||||
scoreTask: _.partial(importedOps.scoreTask, user),
|
score: _.partial(importedOps.scoreTask, user),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
user.fns = {
|
user.fns = {
|
||||||
getItem: _.partial(importedFns.getItem, user),
|
|
||||||
handleTwoHanded: _.partial(importedFns.handleTwoHanded, user),
|
handleTwoHanded: _.partial(importedFns.handleTwoHanded, user),
|
||||||
predictableRandom: _.partial(importedFns.predictableRandom, user),
|
predictableRandom: _.partial(importedFns.predictableRandom, user),
|
||||||
crit: _.partial(importedFns.crit, user),
|
crit: _.partial(importedFns.crit, user),
|
||||||
@@ -276,8 +260,6 @@ api.wrap = function wrapUser (user, main = true) {
|
|||||||
randomDrop: _.partial(importedFns.randomDrop, user),
|
randomDrop: _.partial(importedFns.randomDrop, user),
|
||||||
autoAllocate: _.partial(importedFns.autoAllocate, user),
|
autoAllocate: _.partial(importedFns.autoAllocate, user),
|
||||||
updateStats: _.partial(importedFns.updateStats, user),
|
updateStats: _.partial(importedFns.updateStats, user),
|
||||||
cron: _.partial(importedFns.cron, user),
|
|
||||||
preenUserHistory: _.partial(importedFns.preenUserHistory, user),
|
|
||||||
ultimateGear: _.partial(importedFns.ultimateGear, user),
|
ultimateGear: _.partial(importedFns.ultimateGear, user),
|
||||||
nullify: _.partial(importedFns.nullify, user),
|
nullify: _.partial(importedFns.nullify, user),
|
||||||
};
|
};
|
||||||
@@ -299,6 +281,7 @@ api.wrap = function wrapUser (user, main = true) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
|
// TODO kept for compatibility with the client that relies on v2, remove once the client is adapted
|
||||||
Object.defineProperty(user, 'tasks', {
|
Object.defineProperty(user, 'tasks', {
|
||||||
get () {
|
get () {
|
||||||
let tasks = user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards);
|
let tasks = user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards);
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import _ from 'lodash';
|
|||||||
Are there tags applied?
|
Are there tags applied?
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// TODO move to client
|
||||||
|
|
||||||
module.exports = function(userTags, taskTags) {
|
module.exports = function(userTags, taskTags) {
|
||||||
var arr;
|
var arr;
|
||||||
arr = [];
|
arr = [];
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
module.exports = function(items) {
|
|
||||||
return _.reduce(items, (function(m, v) {
|
|
||||||
return m + (v ? 1 : 0);
|
|
||||||
}), 0);
|
|
||||||
};
|
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
module.exports = function(obj, path) {
|
// TODO remove completely
|
||||||
return _.reduce(path.split('.'), ((function(_this) {
|
|
||||||
return function(curr, next) {
|
module.exports = _.get;
|
||||||
return curr != null ? curr[next] : void 0;
|
|
||||||
};
|
|
||||||
})(this)), obj);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
module.exports = function(obj, path, val) {
|
// TODO remove completely
|
||||||
var arr;
|
|
||||||
arr = path.split('.');
|
module.exports = _.set;
|
||||||
return _.reduce(arr, (function(_this) {
|
|
||||||
return function(curr, next, index) {
|
|
||||||
if ((arr.length - 1) === index) {
|
|
||||||
curr[next] = val;
|
|
||||||
}
|
|
||||||
return curr[next] != null ? curr[next] : curr[next] = {};
|
|
||||||
};
|
|
||||||
})(this), obj);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
/*
|
|
||||||
Encode the download link for .ics iCal file
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function(uid, apiToken) {
|
|
||||||
var loc, ref;
|
|
||||||
loc = (typeof window !== "undefined" && window !== null ? window.location.host : void 0) || (typeof process !== "undefined" && process !== null ? (ref = process.env) != null ? ref.BASE_URL : void 0 : void 0) || '';
|
|
||||||
return encodeURIComponent("http://" + loc + "/v1/users/" + uid + "/calendar.ics?apiToken=" + apiToken);
|
|
||||||
};
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
/*
|
|
||||||
Friendly timestamp
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function(timestamp) {
|
|
||||||
return moment(timestamp).format('MM/DD h:mm:ss a');
|
|
||||||
};
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// TODO move to client
|
||||||
|
|
||||||
module.exports = function(num) {
|
module.exports = function(num) {
|
||||||
if (num) {
|
if (num) {
|
||||||
return Math.floor(num);
|
return Math.floor(num);
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
/*
|
|
||||||
Does user have new chat messages?
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function(messages, lastMessageSeen) {
|
|
||||||
if (!((messages != null ? messages.length : void 0) > 0)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (messages != null ? messages[0] : void 0) && (messages[0].id !== lastMessageSeen);
|
|
||||||
};
|
|
||||||
@@ -4,6 +4,8 @@ import _ from 'lodash';
|
|||||||
are any tags active?
|
are any tags active?
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// TODO move to client
|
||||||
|
|
||||||
module.exports = function(tags) {
|
module.exports = function(tags) {
|
||||||
return _.isEmpty(tags) || _.isEmpty(_.filter(tags, function(t) {
|
return _.isEmpty(tags) || _.isEmpty(_.filter(tags, function(t) {
|
||||||
return t;
|
return t;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// TODO move to client
|
||||||
|
|
||||||
module.exports = function(x, y, dir) {
|
module.exports = function(x, y, dir) {
|
||||||
var roundFn;
|
var roundFn;
|
||||||
switch (dir) {
|
switch (dir) {
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import moment from 'moment';
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
/*
|
|
||||||
Preen history for users with > 7 history entries
|
|
||||||
This takes an infinite array of single day entries [day day day day day...], and turns it into a condensed array
|
|
||||||
of averages, condensing more the further back in time we go. Eg, 7 entries each for last 7 days; 1 entry each week
|
|
||||||
of this month; 1 entry for each month of this year; 1 entry per previous year: [day*7 week*4 month*12 year*infinite]
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function(history) {
|
|
||||||
var newHistory, preen, thisMonth;
|
|
||||||
history = _.filter(history, function(h) {
|
|
||||||
return !!h;
|
|
||||||
});
|
|
||||||
newHistory = [];
|
|
||||||
preen = function(amount, groupBy) {
|
|
||||||
var groups;
|
|
||||||
groups = _.chain(history).groupBy(function(h) {
|
|
||||||
return moment(h.date).format(groupBy);
|
|
||||||
}).sortBy(function(h, k) {
|
|
||||||
return k;
|
|
||||||
}).value();
|
|
||||||
groups = groups.slice(-amount);
|
|
||||||
groups.pop();
|
|
||||||
return _.each(groups, function(group) {
|
|
||||||
newHistory.push({
|
|
||||||
date: moment(group[0].date).toDate(),
|
|
||||||
value: _.reduce(group, (function(m, obj) {
|
|
||||||
return m + obj.value;
|
|
||||||
}), 0) / group.length
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
preen(50, "YYYY");
|
|
||||||
preen(moment().format('MM'), "YYYYMM");
|
|
||||||
thisMonth = moment().format('YYYYMM');
|
|
||||||
newHistory = newHistory.concat(_.filter(history, function(h) {
|
|
||||||
return moment(h.date).format('YYYYMM') === thisMonth;
|
|
||||||
}));
|
|
||||||
return newHistory;
|
|
||||||
};
|
|
||||||
@@ -2,6 +2,7 @@ import moment from 'moment';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
// TODO used only in v2 client
|
// TODO used only in v2 client
|
||||||
|
// TODO test
|
||||||
|
|
||||||
module.exports = function preenTodos (tasks) {
|
module.exports = function preenTodos (tasks) {
|
||||||
return _.filter(tasks, (t) => {
|
return _.filter(tasks, (t) => {
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
/*
|
|
||||||
Remove whitespace #FIXME are we using this anywwhere? Should we be?
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function(str) {
|
|
||||||
if (!str) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return str.replace(/\s/g, '');
|
|
||||||
};
|
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
Silver amount from their money
|
Silver amount from their money
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// TODO move to client
|
||||||
|
|
||||||
module.exports = function(num) {
|
module.exports = function(num) {
|
||||||
if (num) {
|
if (num) {
|
||||||
return ("0" + Math.floor((num - Math.floor(num)) * 100)).slice(-2);
|
return ("0" + Math.floor((num - Math.floor(num)) * 100)).slice(-2);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import moment from 'moment';
|
|||||||
// sending up to the server for performance
|
// sending up to the server for performance
|
||||||
|
|
||||||
// TODO move to client code?
|
// TODO move to client code?
|
||||||
|
// TODO test?
|
||||||
|
|
||||||
const tasksTypes = ['habit', 'daily', 'todo', 'reward'];
|
const tasksTypes = ['habit', 'daily', 'todo', 'reward'];
|
||||||
|
|
||||||
|
|||||||
@@ -28,17 +28,9 @@ const COMMON_FILES = [
|
|||||||
'!./common/script/ops/update.js',
|
'!./common/script/ops/update.js',
|
||||||
'!./common/script/ops/updateWebhook.js',
|
'!./common/script/ops/updateWebhook.js',
|
||||||
'!./common/script/fns/crit.js',
|
'!./common/script/fns/crit.js',
|
||||||
'!./common/script/fns/cron.js',
|
|
||||||
'!./common/script/fns/dotGet.js',
|
|
||||||
'!./common/script/fns/dotSet.js',
|
|
||||||
'!./common/script/fns/getItem.js',
|
|
||||||
'!./common/script/fns/nullify.js',
|
|
||||||
'!./common/script/fns/preenUserHistory.js',
|
|
||||||
'!./common/script/fns/randomDrop.js',
|
'!./common/script/fns/randomDrop.js',
|
||||||
'!./common/script/libs/appliedTags.js',
|
'!./common/script/libs/appliedTags.js',
|
||||||
'!./common/script/libs/countExists.js',
|
'!./common/script/libs/countExists.js',
|
||||||
'!./common/script/libs/dotGet.js',
|
|
||||||
'!./common/script/libs/dotSet.js',
|
|
||||||
'!./common/script/libs/encodeiCalLink.js',
|
'!./common/script/libs/encodeiCalLink.js',
|
||||||
'!./common/script/libs/friendlyTimestamp.js',
|
'!./common/script/libs/friendlyTimestamp.js',
|
||||||
'!./common/script/libs/gold.js',
|
'!./common/script/libs/gold.js',
|
||||||
@@ -46,8 +38,6 @@ const COMMON_FILES = [
|
|||||||
'!./common/script/libs/noTags.js',
|
'!./common/script/libs/noTags.js',
|
||||||
'!./common/script/libs/percent.js',
|
'!./common/script/libs/percent.js',
|
||||||
'!./common/script/libs/planGemLimits.js',
|
'!./common/script/libs/planGemLimits.js',
|
||||||
'!./common/script/libs/preenHistory.js',
|
|
||||||
'!./common/script/libs/removeWhitespace.js',
|
|
||||||
'!./common/script/libs/silver.js',
|
'!./common/script/libs/silver.js',
|
||||||
'!./common/script/libs/splitWhitespace.js',
|
'!./common/script/libs/splitWhitespace.js',
|
||||||
'!./common/script/libs/taskClasses.js',
|
'!./common/script/libs/taskClasses.js',
|
||||||
|
|||||||
@@ -918,6 +918,7 @@ api.batchUpdate = function(req, res, next) {
|
|||||||
return cb();
|
return cb();
|
||||||
};
|
};
|
||||||
if(!api[_req.op]) { return cb(shared.i18n.t('messageUserOperationNotFound', { operation: _req.op })); }
|
if(!api[_req.op]) { return cb(shared.i18n.t('messageUserOperationNotFound', { operation: _req.op })); }
|
||||||
|
|
||||||
api[_req.op](_req, res, cb);
|
api[_req.op](_req, res, cb);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -944,6 +945,7 @@ api.batchUpdate = function(req, res, next) {
|
|||||||
} else if (res.locals.wasModified){
|
} else if (res.locals.wasModified){
|
||||||
// Preen 3-day past-completed To-Dos from Angular & mobile app
|
// Preen 3-day past-completed To-Dos from Angular & mobile app
|
||||||
_user.getTransformedData(function(err, transformedData){
|
_user.getTransformedData(function(err, transformedData){
|
||||||
|
if (err) next(err);
|
||||||
response = transformedData;
|
response = transformedData;
|
||||||
|
|
||||||
response.todos = shared.preenTodos(response.todos);
|
response.todos = shared.preenTodos(response.todos);
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import {
|
|||||||
shouldDo,
|
shouldDo,
|
||||||
} from '../../../../common/script/cron';
|
} from '../../../../common/script/cron';
|
||||||
import common from '../../../../common';
|
import common from '../../../../common';
|
||||||
import Task from '../../models/task';
|
import * as Tasks from '../../models/task';
|
||||||
import Q from 'q';
|
import Q from 'q';
|
||||||
import Group from '../../models/group';
|
import { model as Group } from '../../models/group';
|
||||||
import User from '../../models/user';
|
import { model as User } from '../../models/user';
|
||||||
import { preenUserHistory } from '../../libs/api-v3/preening';
|
import { preenUserHistory } from '../../libs/api-v3/preening';
|
||||||
|
|
||||||
const scoreTask = common.ops.scoreTask;
|
const scoreTask = common.ops.scoreTask;
|
||||||
@@ -108,7 +108,6 @@ function cron (options = {}) {
|
|||||||
direction: 'down',
|
direction: 'down',
|
||||||
cron: true,
|
cron: true,
|
||||||
times: multiDaysCountAsOneDay ? 1 : daysMissed,
|
times: multiDaysCountAsOneDay ? 1 : daysMissed,
|
||||||
// TODO pass req for analytics?
|
|
||||||
});
|
});
|
||||||
|
|
||||||
todoTally += task.value;
|
todoTally += task.value;
|
||||||
@@ -358,7 +357,7 @@ module.exports = function cronMiddleware (req, res, next) {
|
|||||||
if (daysMissed <= 0) return next();
|
if (daysMissed <= 0) return next();
|
||||||
|
|
||||||
// Fetch active tasks (no completed todos)
|
// Fetch active tasks (no completed todos)
|
||||||
Task.find({
|
Tasks.Task.find({
|
||||||
userId: user._id,
|
userId: user._id,
|
||||||
$or: [ // Exclude completed todos
|
$or: [ // Exclude completed todos
|
||||||
{type: 'todo', completed: false},
|
{type: 'todo', completed: false},
|
||||||
@@ -374,7 +373,7 @@ module.exports = function cronMiddleware (req, res, next) {
|
|||||||
|
|
||||||
// Clear old completed todos - 30 days for free users, 90 for subscribers
|
// Clear old completed todos - 30 days for free users, 90 for subscribers
|
||||||
// Do not delete challenges completed todos TODO unless the task is broken?
|
// Do not delete challenges completed todos TODO unless the task is broken?
|
||||||
Task.remove({
|
Tasks.Task.remove({
|
||||||
userId: user._id,
|
userId: user._id,
|
||||||
type: 'todo',
|
type: 'todo',
|
||||||
completed: true,
|
completed: true,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import swagger from 'swagger-node-express';
|
import swagger from 'swagger-node-express';
|
||||||
// import shared from '../../../../common';
|
// import shared from '../../../../common';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
import analytics from './analytics';
|
||||||
|
|
||||||
const v2app = express();
|
const v2app = express();
|
||||||
|
|
||||||
@@ -11,6 +12,8 @@ const v2app = express();
|
|||||||
v2app.set('view engine', 'jade');
|
v2app.set('view engine', 'jade');
|
||||||
v2app.set('views', `${__dirname}/../../../views`);
|
v2app.set('views', `${__dirname}/../../../views`);
|
||||||
|
|
||||||
|
v2app.use(analytics);
|
||||||
|
|
||||||
// Custom Directives
|
// Custom Directives
|
||||||
v2app.use('/', require('../../routes/api-v2/auth'));
|
v2app.use('/', require('../../routes/api-v2/auth'));
|
||||||
v2app.use('/', require('../../routes/api-v2/coupon')); // TODO REMOVE - ONLY v3
|
v2app.use('/', require('../../routes/api-v2/coupon')); // TODO REMOVE - ONLY v3
|
||||||
|
|||||||
Reference in New Issue
Block a user