diff --git a/migrations/archive/mystery-items-old.js b/migrations/archive/mystery-items-old.js new file mode 100644 index 0000000000..d298d9b7e7 --- /dev/null +++ b/migrations/archive/mystery-items-old.js @@ -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; diff --git a/migrations/users/mystery-items.js b/migrations/users/mystery-items.js index d298d9b7e7..8e85ba0a26 100644 --- a/migrations/users/mystery-items.js +++ b/migrations/users/mystery-items.js @@ -1,70 +1,13 @@ -import monk from 'monk'; -import nconf from 'nconf'; +/* eslint-disable no-console */ +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 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; +const 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) { +async function updateUser (user) { count++; const addToSet = { @@ -80,31 +23,49 @@ function updateUser (user) { }, })).toJSON(), }; + const set = { + migration: MIGRATION_NAME, + }; - dbUsers.update({_id: user._id}, {$addToSet: addToSet, $push: push}); + 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 () { - console.warn(`\n${ count } users processed\n`); - return exiting(0); -} +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 } }, + ], + }; -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); + 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`); + break; + } else { + 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 + } +}; diff --git a/website/common/locales/en/gear.json b/website/common/locales/en/gear.json index 2c6563f9bc..61f611c0aa 100644 --- a/website/common/locales/en/gear.json +++ b/website/common/locales/en/gear.json @@ -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.", "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.", + "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", "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.", "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.", + "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", "headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.", "headMystery301405Text": "Basic Top Hat", diff --git a/website/common/locales/en/subscriber.json b/website/common/locales/en/subscriber.json index 973ba65172..f7f134f5b4 100644 --- a/website/common/locales/en/subscriber.json +++ b/website/common/locales/en/subscriber.json @@ -150,6 +150,7 @@ "mysterySet201808": "Lava Dragon Set", "mysterySet201809": "Autumnal Armor Set", "mysterySet201810": "Dark Forest Set", + "mysterySet201811": "Splendid Sorcerer Set", "mysterySet301404": "Steampunk Standard Set", "mysterySet301405": "Steampunk Accessories Set", "mysterySet301703": "Peacock Steampunk Set", diff --git a/website/common/script/content/gear/sets/mystery.js b/website/common/script/content/gear/sets/mystery.js index 2da7b37fba..b1cc0d83b4 100644 --- a/website/common/script/content/gear/sets/mystery.js +++ b/website/common/script/content/gear/sets/mystery.js @@ -643,6 +643,12 @@ let head = { mystery: '201810', value: 0, }, + 201811: { + text: t('headMystery201811Text'), + notes: t('headMystery201811Notes'), + mystery: '201811', + value: 0, + }, 301404: { text: t('headMystery301404Text'), notes: t('headMystery301404Notes'), @@ -796,6 +802,12 @@ let weapon = { mystery: '201708', value: 0, }, + 201811: { + text: t('weaponMystery201811Text'), + notes: t('weaponMystery201811Notes'), + mystery: '201811', + value: 0, + }, 301404: { text: t('weaponMystery301404Text'), notes: t('weaponMystery301404Notes'), diff --git a/website/common/script/content/mystery-sets.js b/website/common/script/content/mystery-sets.js index 1c01b0eeaf..9b81dbed1c 100644 --- a/website/common/script/content/mystery-sets.js +++ b/website/common/script/content/mystery-sets.js @@ -230,6 +230,10 @@ let mysterySets = { start: '2018-10-25', end: '2018-11-02', }, + 201811: { + start: '2018-11-28', + end: '2018-12-4', + }, 301404: { start: '3014-03-24', end: '3014-04-02', diff --git a/website/raw_sprites/spritesmith/gear/events/mystery_201811/head_mystery_201811.png b/website/raw_sprites/spritesmith/gear/events/mystery_201811/head_mystery_201811.png new file mode 100644 index 0000000000..34af551be4 Binary files /dev/null and b/website/raw_sprites/spritesmith/gear/events/mystery_201811/head_mystery_201811.png differ diff --git a/website/raw_sprites/spritesmith/gear/events/mystery_201811/shop_head_mystery_201811.png b/website/raw_sprites/spritesmith/gear/events/mystery_201811/shop_head_mystery_201811.png new file mode 100644 index 0000000000..641b9d098c Binary files /dev/null and b/website/raw_sprites/spritesmith/gear/events/mystery_201811/shop_head_mystery_201811.png differ diff --git a/website/raw_sprites/spritesmith/gear/events/mystery_201811/shop_set_mystery_201811.png b/website/raw_sprites/spritesmith/gear/events/mystery_201811/shop_set_mystery_201811.png new file mode 100644 index 0000000000..a7aa2bc653 Binary files /dev/null and b/website/raw_sprites/spritesmith/gear/events/mystery_201811/shop_set_mystery_201811.png differ diff --git a/website/raw_sprites/spritesmith/gear/events/mystery_201811/shop_weapon_mystery_201811.png b/website/raw_sprites/spritesmith/gear/events/mystery_201811/shop_weapon_mystery_201811.png new file mode 100644 index 0000000000..16207e5a07 Binary files /dev/null and b/website/raw_sprites/spritesmith/gear/events/mystery_201811/shop_weapon_mystery_201811.png differ diff --git a/website/raw_sprites/spritesmith/gear/events/mystery_201811/weapon_mystery_201811.png b/website/raw_sprites/spritesmith/gear/events/mystery_201811/weapon_mystery_201811.png new file mode 100644 index 0000000000..298f7adb82 Binary files /dev/null and b/website/raw_sprites/spritesmith/gear/events/mystery_201811/weapon_mystery_201811.png differ diff --git a/website/raw_sprites/spritesmith_large/promo_mystery_201810.png b/website/raw_sprites/spritesmith_large/promo_mystery_201810.png deleted file mode 100644 index 0c22b7f470..0000000000 Binary files a/website/raw_sprites/spritesmith_large/promo_mystery_201810.png and /dev/null differ diff --git a/website/raw_sprites/spritesmith_large/promo_mystery_201811.png b/website/raw_sprites/spritesmith_large/promo_mystery_201811.png new file mode 100644 index 0000000000..0066948a0d Binary files /dev/null and b/website/raw_sprites/spritesmith_large/promo_mystery_201811.png differ diff --git a/website/raw_sprites/spritesmith_large/promo_piyo.png b/website/raw_sprites/spritesmith_large/promo_piyo.png new file mode 100644 index 0000000000..c7466c1c50 Binary files /dev/null and b/website/raw_sprites/spritesmith_large/promo_piyo.png differ diff --git a/website/server/controllers/api-v3/news.js b/website/server/controllers/api-v3/news.js index 80eb3a356f..513d92ee62 100644 --- a/website/server/controllers/api-v3/news.js +++ b/website/server/controllers/api-v3/news.js @@ -3,7 +3,7 @@ import { authWithHeaders } from '../../middlewares/auth'; let api = {}; // @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 bailey: false, }; @@ -30,17 +30,19 @@ api.getNews = {
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!
-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!
-Thank you for using Habitica - we really love you all <3
-The November Subscriber Items have been revealed: the Splendid Sorcerer Item Set! You'll receive this item set when you subscribe by December 3. If you're already an active subscriber, reload the site and then head to Inventory > Items to claim your gear!
+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.
+There's a new Staff Spotlight 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.
+