feat(content): mystery items 2018-11

This commit is contained in:
Sabe Jones
2018-11-28 14:50:43 -06:00
parent e6807d36b5
commit f19e69948a
15 changed files with 188 additions and 94 deletions

View File

@@ -0,0 +1,110 @@
import monk from 'monk';
import nconf from 'nconf';
const migrationName = 'mystery-items-201808.js'; // Update per month
const authorName = 'Sabe'; // in case script author needs to know when their ...
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
/*
* Award this month's mystery items to subscribers
*/
const MYSTERY_ITEMS = ['armor_mystery_201810', 'head_mystery_201810'];
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
let UserNotification = require('../../website/server/models/userNotification').model;
function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
let query = {
migration: {$ne: migrationName},
'purchased.plan.customerId': { $ne: null },
$or: [
{ 'purchased.plan.dateTerminated': { $gte: new Date() } },
{ 'purchased.plan.dateTerminated': { $exists: false } },
{ 'purchased.plan.dateTerminated': { $eq: null } },
],
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}
let progressCount = 1000;
let count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
let userPromises = users.map(updateUser);
let lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
const addToSet = {
'purchased.plan.mysteryItems': {
$each: MYSTERY_ITEMS,
},
};
const push = {
notifications: (new UserNotification({
type: 'NEW_MYSTERY_ITEMS',
data: {
MYSTERY_ITEMS,
},
})).toJSON(),
};
dbUsers.update({_id: user._id}, {$addToSet: addToSet, $push: push});
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`);
}
function displayData () {
console.warn(`\n${ count } users processed\n`);
return exiting(0);
}
function exiting (code, msg) {
code = code || 0; // 0 = success
if (code && !msg) {
msg = 'ERROR!';
}
if (msg) {
if (code) {
console.error(msg);
} else {
console.log(msg);
}
}
process.exit(code);
}
module.exports = processUsers;

View File

@@ -1,70 +1,13 @@
import monk from 'monk'; /* eslint-disable no-console */
import nconf from 'nconf'; const MIGRATION_NAME = 'mystery_items_201811';
const MYSTERY_ITEMS = ['head_mystery_201811', 'weapon_mystery_201811'];
import { model as User } from '../../website/server/models/user';
import { model as UserNotification } from '../../website/server/models/userNotification';
const migrationName = 'mystery-items-201808.js'; // Update per month const progressCount = 1000;
const authorName = 'Sabe'; // in case script author needs to know when their ...
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
/*
* Award this month's mystery items to subscribers
*/
const MYSTERY_ITEMS = ['armor_mystery_201810', 'head_mystery_201810'];
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
let UserNotification = require('../../website/server/models/userNotification').model;
function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
let query = {
migration: {$ne: migrationName},
'purchased.plan.customerId': { $ne: null },
$or: [
{ 'purchased.plan.dateTerminated': { $gte: new Date() } },
{ 'purchased.plan.dateTerminated': { $exists: false } },
{ 'purchased.plan.dateTerminated': { $eq: null } },
],
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}
let progressCount = 1000;
let count = 0; let count = 0;
function updateUsers (users) { async function updateUser (user) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
let userPromises = users.map(updateUser);
let lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++; count++;
const addToSet = { const addToSet = {
@@ -80,31 +23,49 @@ function updateUser (user) {
}, },
})).toJSON(), })).toJSON(),
}; };
const set = {
dbUsers.update({_id: user._id}, {$addToSet: addToSet, $push: push}); migration: MIGRATION_NAME,
};
if (count % progressCount === 0) console.warn(`${count} ${user._id}`); if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`);
return await User.update({_id: user._id}, {$set: set, $push: push, $addToSet: addToSet}).exec();
} }
function displayData () { module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'purchased.plan.customerId': { $ne: null },
$or: [
{ 'purchased.plan.dateTerminated': { $gte: new Date() } },
{ 'purchased.plan.dateTerminated': { $exists: false } },
{ 'purchased.plan.dateTerminated': { $eq: null } },
],
};
const fields = {
_id: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`); console.warn(`\n${count} users processed\n`);
return exiting(0); break;
}
function exiting (code, msg) {
code = code || 0; // 0 = success
if (code && !msg) {
msg = 'ERROR!';
}
if (msg) {
if (code) {
console.error(msg);
} else { } else {
console.log(msg); query._id = {
} $gt: users[users.length - 1],
} };
process.exit(code);
} }
module.exports = processUsers; await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -313,6 +313,8 @@
"weaponMystery201611Notes": "All manner of delicious and wholesome foods spill forth from this horn. Enjoy the feast! Confers no benefit. November 2016 Subscriber Item.", "weaponMystery201611Notes": "All manner of delicious and wholesome foods spill forth from this horn. Enjoy the feast! Confers no benefit. November 2016 Subscriber Item.",
"weaponMystery201708Text": "Lava Sword", "weaponMystery201708Text": "Lava Sword",
"weaponMystery201708Notes": "The fiery glow of this sword will make quick work of even dark red Tasks! Confers no benefit. August 2017 Subscriber Item.", "weaponMystery201708Notes": "The fiery glow of this sword will make quick work of even dark red Tasks! Confers no benefit. August 2017 Subscriber Item.",
"weaponMystery201811Text": "Splendid Sorcerer's Staff",
"weaponMystery201811Notes": "This magical stave is as powerful as it is elegant. Confers no benefit. November 2018 Subscriber Item.",
"weaponMystery301404Text": "Steampunk Cane", "weaponMystery301404Text": "Steampunk Cane",
"weaponMystery301404Notes": "Excellent for taking a turn about town. March 3015 Subscriber Item. Confers no benefit.", "weaponMystery301404Notes": "Excellent for taking a turn about town. March 3015 Subscriber Item. Confers no benefit.",
@@ -1209,6 +1211,8 @@
"headMystery201809Notes": "The last flowers of autumn's warm days are a reminder of the beauty of the season. Confers no benefit. September 2018 Subscriber Item.", "headMystery201809Notes": "The last flowers of autumn's warm days are a reminder of the beauty of the season. Confers no benefit. September 2018 Subscriber Item.",
"headMystery201810Text": "Dark Forest Helm", "headMystery201810Text": "Dark Forest Helm",
"headMystery201810Notes": "If you find yourself traveling through a spooky place, the glowing red eyes of this helm will surely scare away any enemies in your path. Confers no benefit. October 2018 Subscriber Item.", "headMystery201810Notes": "If you find yourself traveling through a spooky place, the glowing red eyes of this helm will surely scare away any enemies in your path. Confers no benefit. October 2018 Subscriber Item.",
"headMystery201811Text": "Splendid Sorcerer's Hat",
"headMystery201811Notes": "Wear this feathered hat to stand out at even the fanciest wizardly gatherings! Confers no benefit. November 2018 Subscriber Item.",
"headMystery301404Text": "Fancy Top Hat", "headMystery301404Text": "Fancy Top Hat",
"headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.", "headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.",
"headMystery301405Text": "Basic Top Hat", "headMystery301405Text": "Basic Top Hat",

View File

@@ -150,6 +150,7 @@
"mysterySet201808": "Lava Dragon Set", "mysterySet201808": "Lava Dragon Set",
"mysterySet201809": "Autumnal Armor Set", "mysterySet201809": "Autumnal Armor Set",
"mysterySet201810": "Dark Forest Set", "mysterySet201810": "Dark Forest Set",
"mysterySet201811": "Splendid Sorcerer Set",
"mysterySet301404": "Steampunk Standard Set", "mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set", "mysterySet301405": "Steampunk Accessories Set",
"mysterySet301703": "Peacock Steampunk Set", "mysterySet301703": "Peacock Steampunk Set",

View File

@@ -643,6 +643,12 @@ let head = {
mystery: '201810', mystery: '201810',
value: 0, value: 0,
}, },
201811: {
text: t('headMystery201811Text'),
notes: t('headMystery201811Notes'),
mystery: '201811',
value: 0,
},
301404: { 301404: {
text: t('headMystery301404Text'), text: t('headMystery301404Text'),
notes: t('headMystery301404Notes'), notes: t('headMystery301404Notes'),
@@ -796,6 +802,12 @@ let weapon = {
mystery: '201708', mystery: '201708',
value: 0, value: 0,
}, },
201811: {
text: t('weaponMystery201811Text'),
notes: t('weaponMystery201811Notes'),
mystery: '201811',
value: 0,
},
301404: { 301404: {
text: t('weaponMystery301404Text'), text: t('weaponMystery301404Text'),
notes: t('weaponMystery301404Notes'), notes: t('weaponMystery301404Notes'),

View File

@@ -230,6 +230,10 @@ let mysterySets = {
start: '2018-10-25', start: '2018-10-25',
end: '2018-11-02', end: '2018-11-02',
}, },
201811: {
start: '2018-11-28',
end: '2018-12-4',
},
301404: { 301404: {
start: '3014-03-24', start: '3014-03-24',
end: '3014-04-02', end: '3014-04-02',

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -3,7 +3,7 @@ import { authWithHeaders } from '../../middlewares/auth';
let api = {}; let api = {};
// @TODO export this const, cannot export it from here because only routes are exported from controllers // @TODO export this const, cannot export it from here because only routes are exported from controllers
const LAST_ANNOUNCEMENT_TITLE = 'HAPPY THANKSGIVING!'; const LAST_ANNOUNCEMENT_TITLE = 'NOVEMBER SUBSCRIBER ITEMS AND STAFF SPOTLIGHT!';
const worldDmg = { // @TODO const worldDmg = { // @TODO
bailey: false, bailey: false,
}; };
@@ -30,17 +30,19 @@ api.getNews = {
<div class="mr-3 ${baileyClass}"></div> <div class="mr-3 ${baileyClass}"></div>
<div class="media-body"> <div class="media-body">
<h1 class="align-self-center">${res.t('newStuff')}</h1> <h1 class="align-self-center">${res.t('newStuff')}</h1>
<h2>11/22/2018 - ${LAST_ANNOUNCEMENT_TITLE}</h2> <h2>11/28/2018 - ${LAST_ANNOUNCEMENT_TITLE}</h2>
</div> </div>
</div> </div>
<hr/> <hr/>
<div class="npc_matt center-block"></div> <div class="promo_mystery_201811 center-block"></div>
<p>It's Thanksgiving in Habitica! On this day Habiticans celebrate by spending time with loved ones, giving thanks, and riding their glorious turkeys into the magnificent sunset. Some of the NPCs are celebrating the occasion!</p> <h3>November Subscriber Items Revealed!</h3>
<h3>Turkey Pet, Mount, and Costumes!</h3> <p>The November Subscriber Items have been revealed: the Splendid Sorcerer Item Set! You'll receive this item set when you <a href='/user/settings/subscription' target='_blank'>subscribe</a> by December 3. If you're already an active subscriber, reload the site and then head to Inventory > Items to claim your gear!</p>
<p>In celebration of Turkey Day, everyone has received an adorable Turkey! What kind of Turkey? It all depends on how many Habitica Thanksgivings you've celebrated with us. Each Thanksgiving, you'll get a new and exciting Turkey variety!</p> <p>Subscribers also receive the ability to buy Gems for Gold -- the longer you subscribe, the more Gems you can buy per month! There are other perks as well, such as longer access to uncompressed data and a cute Jackalope pet. Best of all, subscriptions let us keep Habitica running. Thank you very much for your support -- it means a lot to us.</p>
<p>Thank you for using Habitica - we really love you all <3</p> <div class="small mb-3">by Beffymaroo</div>
<div class="small mb-3">by Lemoness and Beffymaroo</div> <div class="promo_piyo center-block"></div>
<div class="promo_turkey_day_2018 center-block"></div> <h3>Staff Spotlight: Sara, aka Piyo</h3>
<p>There's a new <a href='https://habitica.wordpress.com/2018/11/20/staff-spotlight-sara-aka-piyo/' target='_blank'>Staff Spotlight</a> on the blog! Come meet Sara, aka Piyo, and learn how our favorite onesie-wearing cryptid balances her design work for Habitica with her enthusiasm for baking and video games.</p>
<div class="small mb-3">by Beffymaroo</div>
</div> </div>
`, `,
}); });