mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-15 05:37:22 +01:00
Award mystery items at cron (#11519)
* feat(subscriber): award mystery items at cron * fix(mystery): backfill skipped months * fix(mystery): adjust subscriber text * fix(mystery): correct moment logic and remove irrelevant tests * fix(mystery): build set one month in advance * fix(mystery): don't add empty set to Time Travelers * refactor(mystery): readability
This commit is contained in:
@@ -88,6 +88,28 @@ describe('cron', () => {
|
||||
user.purchased.plan.dateUpdated = moment().subtract(1, 'months').toDate();
|
||||
});
|
||||
|
||||
it('awards current mystery items to subscriber', () => {
|
||||
user.purchased.plan.dateUpdated = new Date('2018-12-11');
|
||||
clock = sinon.useFakeTimers(new Date('2019-01-29'));
|
||||
cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user.purchased.plan.mysteryItems.length).to.eql(2);
|
||||
expect(user.notifications.length).to.eql(3);
|
||||
expect(user.notifications[0].type).to.eql('NEW_MYSTERY_ITEMS');
|
||||
});
|
||||
|
||||
it('awards multiple mystery item sets if user skipped months between logins', () => {
|
||||
user.purchased.plan.dateUpdated = new Date('2018-11-11');
|
||||
clock = sinon.useFakeTimers(new Date('2019-01-29'));
|
||||
cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(user.purchased.plan.mysteryItems.length).to.eql(4);
|
||||
expect(user.notifications.length).to.eql(3);
|
||||
expect(user.notifications[0].type).to.eql('NEW_MYSTERY_ITEMS');
|
||||
});
|
||||
|
||||
it('resets plan.gemsBought on a new month', () => {
|
||||
user.purchased.plan.gemsBought = 10;
|
||||
cron({
|
||||
|
||||
@@ -439,31 +439,6 @@ describe('payments/index', () => {
|
||||
fakeClock.restore();
|
||||
});
|
||||
|
||||
it('does not awards mystery items when not within the timeframe for a mystery item', async () => {
|
||||
const noMysteryItemTimeframe = 1462183920000; // May 2nd 2016
|
||||
const fakeClock = sinon.useFakeTimers(noMysteryItemTimeframe);
|
||||
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(0);
|
||||
|
||||
fakeClock.restore();
|
||||
});
|
||||
|
||||
it('does not add a notification for mystery items if none was awarded', async () => {
|
||||
const noMysteryItemTimeframe = 1462183920000; // May 2nd 2016
|
||||
const fakeClock = sinon.useFakeTimers(noMysteryItemTimeframe);
|
||||
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(0);
|
||||
expect(user.notifications.find(n => n.type === 'NEW_MYSTERY_ITEMS')).to.be.undefined;
|
||||
|
||||
fakeClock.restore();
|
||||
});
|
||||
|
||||
it('does not award mystery item when user already owns the item', async () => {
|
||||
const mayMysteryItemTimeframe = 1464725113000; // May 31st 2016
|
||||
const fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe);
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"newGroupTitle": "New Group",
|
||||
"subscriberItem": "Mystery Item",
|
||||
"newSubscriberItem": "You have new <span class=\"notification-bold-blue\">Mystery Items</span>",
|
||||
"subscriberItemText": "Each month, subscribers will receive a mystery item. This is usually released about one week before the end of the month. See the wiki's 'Mystery Item' page for more information.",
|
||||
"subscriberItemText": "Each month, subscribers will receive a mystery item. It becomes available at the beginning of the month. See the wiki's 'Mystery Item' page for more information.",
|
||||
"all": "All",
|
||||
"none": "None",
|
||||
"more": "<%= count %> more",
|
||||
|
||||
@@ -1,287 +1,8 @@
|
||||
import each from 'lodash/each';
|
||||
import moment from 'moment';
|
||||
import t from './translation';
|
||||
|
||||
const mysterySets = {
|
||||
201402: {
|
||||
start: '2014-02-22',
|
||||
end: '2014-02-28',
|
||||
},
|
||||
201403: {
|
||||
start: '2014-03-24',
|
||||
end: '2014-04-02',
|
||||
},
|
||||
201404: {
|
||||
start: '2014-04-24',
|
||||
end: '2014-05-02',
|
||||
},
|
||||
201405: {
|
||||
start: '2014-05-21',
|
||||
end: '2014-06-02',
|
||||
},
|
||||
201406: {
|
||||
start: '2014-06-23',
|
||||
end: '2014-07-02',
|
||||
},
|
||||
201407: {
|
||||
start: '2014-07-23',
|
||||
end: '2014-08-02',
|
||||
},
|
||||
201408: {
|
||||
start: '2014-08-23',
|
||||
end: '2014-09-02',
|
||||
},
|
||||
201409: {
|
||||
start: '2014-09-24',
|
||||
end: '2014-10-02',
|
||||
},
|
||||
201410: {
|
||||
start: '2014-10-24',
|
||||
end: '2014-11-02',
|
||||
},
|
||||
201411: {
|
||||
start: '2014-11-24',
|
||||
end: '2014-12-02',
|
||||
},
|
||||
201412: {
|
||||
start: '2014-12-25',
|
||||
end: '2015-01-02',
|
||||
},
|
||||
201501: {
|
||||
start: '2015-01-26',
|
||||
end: '2015-02-02',
|
||||
},
|
||||
201502: {
|
||||
start: '2015-02-24',
|
||||
end: '2015-03-02',
|
||||
},
|
||||
201503: {
|
||||
start: '2015-03-25',
|
||||
end: '2015-04-02',
|
||||
},
|
||||
201504: {
|
||||
start: '2015-04-24',
|
||||
end: '2015-05-02',
|
||||
},
|
||||
201505: {
|
||||
start: '2015-05-25',
|
||||
end: '2015-06-02',
|
||||
},
|
||||
201506: {
|
||||
start: '2015-06-25',
|
||||
end: '2015-07-02',
|
||||
},
|
||||
201507: {
|
||||
start: '2015-07-24',
|
||||
end: '2015-08-02',
|
||||
},
|
||||
201508: {
|
||||
start: '2015-08-23',
|
||||
end: '2015-09-02',
|
||||
},
|
||||
201509: {
|
||||
start: '2015-09-24',
|
||||
end: '2015-10-02',
|
||||
},
|
||||
201510: {
|
||||
start: '2015-10-26',
|
||||
end: '2015-11-02',
|
||||
},
|
||||
201511: {
|
||||
start: '2015-11-25',
|
||||
end: '2015-12-02',
|
||||
},
|
||||
201512: {
|
||||
start: '2015-12-23',
|
||||
end: '2016-01-02',
|
||||
},
|
||||
201601: {
|
||||
start: '2016-01-26',
|
||||
end: '2016-02-02',
|
||||
},
|
||||
201602: {
|
||||
start: '2016-02-24',
|
||||
end: '2016-03-02',
|
||||
},
|
||||
201603: {
|
||||
start: '2016-03-24',
|
||||
end: '2016-04-02',
|
||||
},
|
||||
201604: {
|
||||
start: '2016-04-25',
|
||||
end: '2016-05-02',
|
||||
},
|
||||
201605: {
|
||||
start: '2016-05-25',
|
||||
end: '2016-06-02',
|
||||
},
|
||||
201606: {
|
||||
start: '2016-06-23',
|
||||
end: '2016-07-02',
|
||||
},
|
||||
201607: {
|
||||
start: '2016-07-26',
|
||||
end: '2016-08-02',
|
||||
},
|
||||
201608: {
|
||||
start: '2016-08-23',
|
||||
end: '2016-09-02',
|
||||
},
|
||||
201609: {
|
||||
start: '2016-09-22',
|
||||
end: '2016-10-02',
|
||||
},
|
||||
201610: {
|
||||
start: '2016-10-25',
|
||||
end: '2016-11-02',
|
||||
},
|
||||
201611: {
|
||||
start: '2016-11-22',
|
||||
end: '2016-12-02',
|
||||
},
|
||||
201612: {
|
||||
start: '2016-12-21',
|
||||
end: '2017-01-02',
|
||||
},
|
||||
201701: {
|
||||
start: '2017-01-24',
|
||||
end: '2017-02-02',
|
||||
},
|
||||
201702: {
|
||||
start: '2017-02-21',
|
||||
end: '2017-03-02',
|
||||
},
|
||||
201703: {
|
||||
start: '2017-03-23',
|
||||
end: '2017-04-02',
|
||||
},
|
||||
201704: {
|
||||
start: '2017-04-25',
|
||||
end: '2017-05-02',
|
||||
},
|
||||
201705: {
|
||||
start: '2017-05-23',
|
||||
end: '2017-06-02',
|
||||
},
|
||||
201706: {
|
||||
start: '2017-06-22',
|
||||
end: '2017-07-02',
|
||||
},
|
||||
201707: {
|
||||
start: '2017-07-25',
|
||||
end: '2017-08-02',
|
||||
},
|
||||
201708: {
|
||||
start: '2017-08-17',
|
||||
end: '2017-09-02',
|
||||
},
|
||||
201709: {
|
||||
start: '2017-09-19',
|
||||
end: '2017-10-07',
|
||||
},
|
||||
201710: {
|
||||
start: '2017-10-24',
|
||||
end: '2017-11-02',
|
||||
},
|
||||
201711: {
|
||||
start: '2017-11-17',
|
||||
end: '2017-12-02',
|
||||
},
|
||||
201712: {
|
||||
start: '2017-12-21',
|
||||
end: '2018-01-02',
|
||||
},
|
||||
201801: {
|
||||
start: '2018-01-23',
|
||||
end: '2018-02-02',
|
||||
},
|
||||
201802: {
|
||||
start: '2018-02-22',
|
||||
end: '2018-03-02',
|
||||
},
|
||||
201803: {
|
||||
start: '2018-03-22',
|
||||
end: '2018-04-02',
|
||||
},
|
||||
201804: {
|
||||
start: '2018-04-24',
|
||||
end: '2018-05-02',
|
||||
},
|
||||
201805: {
|
||||
start: '2018-05-24',
|
||||
end: '2018-06-02',
|
||||
},
|
||||
201806: {
|
||||
start: '2018-06-21',
|
||||
end: '2018-07-02',
|
||||
},
|
||||
201807: {
|
||||
start: '2018-07-24',
|
||||
end: '2018-08-02',
|
||||
},
|
||||
201808: {
|
||||
start: '2018-08-23',
|
||||
end: '2018-09-02',
|
||||
},
|
||||
201809: {
|
||||
start: '2018-09-25',
|
||||
end: '2018-10-02',
|
||||
},
|
||||
201810: {
|
||||
start: '2018-10-25',
|
||||
end: '2018-11-02',
|
||||
},
|
||||
201811: {
|
||||
start: '2018-11-28',
|
||||
end: '2018-12-4',
|
||||
},
|
||||
201812: {
|
||||
start: '2018-12-19',
|
||||
end: '2019-01-02',
|
||||
},
|
||||
201901: {
|
||||
start: '2019-01-28',
|
||||
end: '2019-02-02',
|
||||
},
|
||||
201902: {
|
||||
start: '2019-02-25',
|
||||
end: '2019-03-02',
|
||||
},
|
||||
201903: {
|
||||
start: '2019-03-26',
|
||||
end: '2019-04-02',
|
||||
},
|
||||
201904: {
|
||||
start: '2019-04-25',
|
||||
end: '2019-05-02',
|
||||
},
|
||||
201905: {
|
||||
start: '2019-05-28',
|
||||
end: '2019-06-02',
|
||||
},
|
||||
201906: {
|
||||
start: '2019-06-25',
|
||||
end: '2019-07-03',
|
||||
},
|
||||
201907: {
|
||||
start: '2019-07-25',
|
||||
end: '2019-08-02',
|
||||
},
|
||||
201908: {
|
||||
start: '2019-08-27',
|
||||
end: '2019-09-02',
|
||||
},
|
||||
201909: {
|
||||
start: '2019-09-25',
|
||||
end: '2019-10-02',
|
||||
},
|
||||
201910: {
|
||||
start: '2019-10-24',
|
||||
end: '2019-11-02',
|
||||
},
|
||||
201911: {
|
||||
start: '2019-11-21',
|
||||
end: '2019-12-02',
|
||||
},
|
||||
301404: {
|
||||
start: '3014-03-24',
|
||||
end: '3014-04-02',
|
||||
@@ -304,6 +25,19 @@ const mysterySets = {
|
||||
end: '2014-04-01',
|
||||
},
|
||||
};
|
||||
const FIRST_MYSTERY_SET = moment('2014-02-01');
|
||||
|
||||
for (
|
||||
let mysteryMonth = FIRST_MYSTERY_SET;
|
||||
moment(mysteryMonth).startOf('month').isSameOrBefore(moment().add(1, 'months'));
|
||||
mysteryMonth = moment(mysteryMonth).add(1, 'months')
|
||||
) {
|
||||
const setKey = moment(mysteryMonth).format('YYYYMM');
|
||||
mysterySets[setKey] = {
|
||||
start: moment(mysteryMonth).startOf('month').format('YYYY-MM-DD'),
|
||||
end: moment(mysteryMonth).endOf('month').format('YYYY-MM-DD'),
|
||||
};
|
||||
}
|
||||
|
||||
each(mysterySets, (value, key) => {
|
||||
value.key = key;
|
||||
|
||||
@@ -13,6 +13,7 @@ each(mystery, (v, k) => {
|
||||
v.items = filter(gear.flat, {
|
||||
mystery: k,
|
||||
});
|
||||
if (v.items.length === 0) delete mystery[k];
|
||||
});
|
||||
|
||||
const timeTravelerStore = user => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { model as User } from '../models/user';
|
||||
import common from '../../common';
|
||||
import { preenUserHistory } from './preening';
|
||||
import sleep from './sleep';
|
||||
import { revealMysteryItems } from './payments/subscriptions';
|
||||
|
||||
const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
|
||||
const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true';
|
||||
@@ -75,6 +76,9 @@ function grantEndOfTheMonthPerks (user, now) {
|
||||
count: 0, offset: 0, trinkets: 0, gemCapExtra: 0,
|
||||
});
|
||||
|
||||
// Award mystery items
|
||||
revealMysteryItems(user, elapsedMonths);
|
||||
|
||||
// 1 for one-month recurring or gift subscriptions; later set to 3 for 3-month recurring, etc.
|
||||
let planMonthsLength = 1;
|
||||
|
||||
|
||||
@@ -21,14 +21,13 @@ import { sendNotification as sendPushNotification } from '../pushNotifications';
|
||||
// @TODO: Abstract to shared/constant
|
||||
const JOINED_GROUP_PLAN = 'joined group plan';
|
||||
|
||||
function revealMysteryItems (user) {
|
||||
function _findMysteryItems (user, dateMoment) {
|
||||
const pushedItems = [];
|
||||
|
||||
_.each(shared.content.gear.flat, item => {
|
||||
if (
|
||||
item.klass === 'mystery'
|
||||
&& moment().isAfter(shared.content.mystery[item.mystery].start)
|
||||
&& moment().isBefore(shared.content.mystery[item.mystery].end)
|
||||
&& dateMoment.isSameOrAfter(shared.content.mystery[item.mystery].start)
|
||||
&& dateMoment.isSameOrBefore(moment(shared.content.mystery[item.mystery].end).endOf('day'))
|
||||
&& !user.items.gear.owned[item.key]
|
||||
&& user.purchased.plan.mysteryItems.indexOf(item.key) === -1
|
||||
) {
|
||||
@@ -36,6 +35,19 @@ function revealMysteryItems (user) {
|
||||
pushedItems.push(item.key);
|
||||
}
|
||||
});
|
||||
return pushedItems;
|
||||
}
|
||||
|
||||
function revealMysteryItems (user, elapsedMonths = 1) {
|
||||
let monthsToCheck = elapsedMonths;
|
||||
let pushedItems = [];
|
||||
|
||||
do {
|
||||
monthsToCheck -= 1;
|
||||
pushedItems = pushedItems.concat(_findMysteryItems(user, moment().subtract(monthsToCheck, 'months')));
|
||||
}
|
||||
while (monthsToCheck > 0);
|
||||
|
||||
if (pushedItems.length > 0) {
|
||||
user.addNotification('NEW_MYSTERY_ITEMS', { items: pushedItems });
|
||||
}
|
||||
@@ -324,4 +336,5 @@ async function cancelSubscription (data) {
|
||||
export {
|
||||
createSubscription,
|
||||
cancelSubscription,
|
||||
revealMysteryItems,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user