From a35f04be46283568ae55494e40564caccd7b35e1 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 5 Oct 2018 19:34:21 +0200 Subject: [PATCH 01/13] migrations: move inbox migration to archive --- .../2018}/20180811_inboxOutsideUser.js | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) rename migrations/{ => archive/2018}/20180811_inboxOutsideUser.js (79%) diff --git a/migrations/20180811_inboxOutsideUser.js b/migrations/archive/2018/20180811_inboxOutsideUser.js similarity index 79% rename from migrations/20180811_inboxOutsideUser.js rename to migrations/archive/2018/20180811_inboxOutsideUser.js index 29a1061827..a58df2a4cf 100644 --- a/migrations/20180811_inboxOutsideUser.js +++ b/migrations/archive/2018/20180811_inboxOutsideUser.js @@ -8,6 +8,7 @@ const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is do const monk = require('monk'); const nconf = require('nconf'); +const uuid = require('uuid').v4; const Inbox = require('../website/server/models/message').inboxModel; const connectionString = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE @@ -84,16 +85,39 @@ function updateUser (user) { return newMsg.toJSON(); }); - return dbInboxes.insert(newInboxMessages) - .then(() => { + const promises = newInboxMessages.map(newMsg => { + return (async function fn () { + const existing = await dbInboxes.find({_id: newMsg._id}); + + if (existing.length > 0) { + if ( + existing[0].ownerId === newMsg.ownerId && + existing[0].text === newMsg.text && + existing[0].uuid === newMsg.uuid && + existing[0].sent === newMsg.sent + ) { + return null; + } + + newMsg.id = newMsg._id = uuid(); + } + + return newMsg; + })(); + }); + + return Promise.all(promises) + .then((filteredNewMsg) => { + filteredNewMsg = filteredNewMsg.filter(m => Boolean(m && m.id && m._id && m.id == m._id)); + return dbInboxes.insert(filteredNewMsg); + }).then(() => { return dbUsers.update({_id: user._id}, { $set: { migration: migrationName, 'inbox.messages': {}, }, }); - }) - .catch((err) => { + }).catch((err) => { console.log(err); return exiting(1, `ERROR! ${ err}`); }); From c880596a7737872b526015f99d5a2ac19ab87d98 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 5 Oct 2018 19:34:42 +0200 Subject: [PATCH 02/13] Cleanup after inbox migration (#10487) --- .../user/DELETE-user_messages.test.js | 6 ++--- test/api/v4/user/GET-user.test.js | 2 +- website/server/libs/inbox/index.js | 25 +++++-------------- website/server/models/user/schema.js | 6 +---- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/test/api/v3/integration/user/DELETE-user_messages.test.js b/test/api/v3/integration/user/DELETE-user_messages.test.js index 3b5686c03b..079550b136 100644 --- a/test/api/v3/integration/user/DELETE-user_messages.test.js +++ b/test/api/v3/integration/user/DELETE-user_messages.test.js @@ -20,8 +20,8 @@ describe('DELETE user message', () => { messagesId = Object.keys(userRes.inbox.messages); expect(messagesId.length).to.eql(2); - expect(userRes.inbox.messages[messagesId[0]].text).to.eql('first'); - expect(userRes.inbox.messages[messagesId[1]].text).to.eql('second'); + expect(userRes.inbox.messages[messagesId[0]].text).to.eql('second'); + expect(userRes.inbox.messages[messagesId[1]].text).to.eql('first'); }); it('one message', async () => { @@ -31,7 +31,7 @@ describe('DELETE user message', () => { let userRes = await user.get('/user'); expect(Object.keys(userRes.inbox.messages).length).to.eql(1); - expect(userRes.inbox.messages[messagesId[0]].text).to.eql('second'); + expect(userRes.inbox.messages[messagesId[0]].text).to.eql('first'); }); it('clear all', async () => { diff --git a/test/api/v4/user/GET-user.test.js b/test/api/v4/user/GET-user.test.js index bf6cd2bb50..4b1bad4054 100644 --- a/test/api/v4/user/GET-user.test.js +++ b/test/api/v4/user/GET-user.test.js @@ -53,6 +53,6 @@ describe('GET /user', () => { let returnedUser = await user.get('/user'); expect(returnedUser._id).to.equal(user._id); - expect(returnedUser.inbox.messages).to.be.empty; + expect(returnedUser.inbox.messages).to.be.undefined; }); }); diff --git a/website/server/libs/inbox/index.js b/website/server/libs/inbox/index.js index 5f5fd76658..cbdda9d8e2 100644 --- a/website/server/libs/inbox/index.js +++ b/website/server/libs/inbox/index.js @@ -1,18 +1,15 @@ import { inboxModel as Inbox } from '../../models/message'; -import { toArray, orderBy } from 'lodash'; export async function getUserInbox (user, asArray = true) { const messages = (await Inbox .find({ownerId: user._id}) + .sort({timestamp: -1}) .exec()).map(msg => msg.toJSON()); - const messagesObj = Object.assign({}, user.inbox.messages); // copy, shallow clone - if (asArray) { - messages.push(...toArray(messagesObj)); - - return orderBy(messages, ['timestamp'], ['desc']); + return messages; } else { + const messagesObj = {}; messages.forEach(msg => messagesObj[msg._id] = msg); return messagesObj; @@ -20,15 +17,9 @@ export async function getUserInbox (user, asArray = true) { } export async function deleteMessage (user, messageId) { - if (user.inbox.messages[messageId]) { // compatibility - delete user.inbox.messages[messageId]; - user.markModified(`inbox.messages.${messageId}`); - await user.save(); - } else { - const message = await Inbox.findOne({_id: messageId, ownerId: user._id }).exec(); - if (!message) return false; - await Inbox.remove({_id: message._id, ownerId: user._id}).exec(); - } + const message = await Inbox.findOne({_id: messageId, ownerId: user._id }).exec(); + if (!message) return false; + await Inbox.remove({_id: message._id, ownerId: user._id}).exec(); return true; } @@ -36,10 +27,6 @@ export async function deleteMessage (user, messageId) { export async function clearPMs (user) { user.inbox.newMessages = 0; - // compatibility - user.inbox.messages = {}; - user.markModified('inbox.messages'); - await Promise.all([ user.save(), Inbox.remove({ownerId: user._id}).exec(), diff --git a/website/server/models/user/schema.js b/website/server/models/user/schema.js index 680ca49fa0..d8517a753d 100644 --- a/website/server/models/user/schema.js +++ b/website/server/models/user/schema.js @@ -559,11 +559,7 @@ let schema = new Schema({ tags: [TagSchema], inbox: { - // messages are stored in the Inbox collection, this path will be removed - // as soon as the migration has run and all the messages have been removed from here - messages: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, + // messages are stored in the Inbox collection newMessages: {type: Number, default: 0}, blocks: {type: Array, default: () => []}, optOut: {type: Boolean, default: false}, From 70192e493568a6aebca926d0a0a6c9dd6ea2ca6e Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sun, 7 Oct 2018 14:20:30 -0500 Subject: [PATCH 03/13] Scripts October 2018 (#10741) * chore(scripts): BTS Challenge archive and username email jobbing * refactor(migration): use batching and sendTxn * fix(script): introduce delay for batching * fix(migration): correct import, fix delay promise, slower batching * fix(migration): add daterange * WIP(script): deletion helper for GDPR * fix(script): address code comments * refactor(script): use for loop * fix(script-runner): bad catch syntax * fix(script-runner): oops I did it again * fix(lint): name functions --- config.json.example | 1 + .../20181001_backtoschool_challenge.js | 48 ++++++++ migrations/migration-runner.js | 9 +- migrations/users/20181002_username_email.js | 107 ++++++++++++++++++ scripts/gdpr-delete-users.js | 82 ++++++++++++++ 5 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 database_reports/20181001_backtoschool_challenge.js create mode 100644 migrations/users/20181002_username_email.js create mode 100644 scripts/gdpr-delete-users.js diff --git a/config.json.example b/config.json.example index 57464ef94a..81cd66622b 100644 --- a/config.json.example +++ b/config.json.example @@ -39,6 +39,7 @@ "NEW_RELIC_API_KEY":"NEW_RELIC_API_KEY", "GA_ID": "GA_ID", "AMPLITUDE_KEY": "AMPLITUDE_KEY", + "AMPLITUDE_SECRET": "AMPLITUDE_SECRET", "AMAZON_PAYMENTS": { "SELLER_ID": "SELLER_ID", "CLIENT_ID": "CLIENT_ID", diff --git a/database_reports/20181001_backtoschool_challenge.js b/database_reports/20181001_backtoschool_challenge.js new file mode 100644 index 0000000000..ec22d1de8d --- /dev/null +++ b/database_reports/20181001_backtoschool_challenge.js @@ -0,0 +1,48 @@ +import monk from 'monk'; +import nconf from 'nconf'; + +/* + * Output data on users who completed all the To-Do tasks in the 2018 Back-to-School Challenge. + * User ID,Profile Name + */ +const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING'); +const CHALLENGE_ID = '0acb1d56-1660-41a4-af80-9259f080b62b'; + +let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false }); +let dbTasks = monk(CONNECTION_STRING).get('tasks', { castIds: false }); + +function usersReport() { + console.info('User ID,Profile Name'); + let userCount = 0; + + dbUsers.find( + {challenges: CHALLENGE_ID}, + {fields: + {_id: 1, 'profile.name': 1} + }, + ).each((user, {close, pause, resume}) => { + pause(); + userCount++; + let completedTodos = 0; + return dbTasks.find( + { + userId: user._id, + 'challenge.id': CHALLENGE_ID, + type: 'todo', + }, + {fields: {completed: 1}} + ).each((task) => { + if (task.completed) completedTodos++; + }).then(() => { + if (completedTodos >= 7) { + console.info(`${user._id},${user.profile.name}`); + } + resume(); + }); + }).then(() => { + console.info(`${userCount} users reviewed`); + return process.exit(0); + }); +} + +module.exports = usersReport; diff --git a/migrations/migration-runner.js b/migrations/migration-runner.js index cc8d33e9b8..e9b62d5c2e 100644 --- a/migrations/migration-runner.js +++ b/migrations/migration-runner.js @@ -18,4 +18,11 @@ setUpServer(); // Replace this with your migration const processUsers = require('./users/takeThis.js'); -processUsers(); +processUsers() + .then(function success () { + process.exitCode = 0; + }) + .catch(function failure (err) { + console.log(err); + process.exitCode = 1; + }); diff --git a/migrations/users/20181002_username_email.js b/migrations/users/20181002_username_email.js new file mode 100644 index 0000000000..908d83682e --- /dev/null +++ b/migrations/users/20181002_username_email.js @@ -0,0 +1,107 @@ +const MIGRATION_NAME = '20181003_username_email.js'; +let authorName = 'Sabe'; // in case script author needs to know when their ... +let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done + +/* + * Send emails to eligible users announcing upcoming username changes + */ + +import monk from 'monk'; +import nconf from 'nconf'; +import { sendTxn } from '../../website/server/libs/email'; +const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING'); +let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false }); + +function processUsers (lastId) { + // specify a query to limit the affected users (empty for all users): + let query = { + migration: {$ne: MIGRATION_NAME}, + 'auth.timestamps.loggedin': {$gt: new Date('2018-04-01')}, + }; + + if (lastId) { + query._id = { + $gt: lastId, + }; + } + + dbUsers.find(query, { + sort: {_id: 1}, + limit: 100, + fields: [ + '_id', + 'auth', + 'preferences', + 'profile', + ], // 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(() => delay(7000)) + .then(() => { + processUsers(lastUser._id); + }); +} + +function updateUser (user) { + count++; + + dbUsers.update({_id: user._id}, {$set: {migration: MIGRATION_NAME}}); + + sendTxn( + user, + 'username-change', + [{name: 'UNSUB_EMAIL_TYPE_URL', content: '/user/settings/notifications?unsubFrom=importantAnnouncements'}, + {name: 'LOGIN_NAME', content: user.auth.local.username}] + ); + + 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 delay (t, v) { + return new Promise(function batchPause (resolve) { + setTimeout(resolve.bind(null, v), t); + }); +} + +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/scripts/gdpr-delete-users.js b/scripts/gdpr-delete-users.js new file mode 100644 index 0000000000..59dd5f5cc2 --- /dev/null +++ b/scripts/gdpr-delete-users.js @@ -0,0 +1,82 @@ +/* eslint-disable no-console */ +import axios from 'axios'; +import { model as User } from '../website/server/models/user'; +import nconf from 'nconf'; + +const AMPLITUDE_KEY = nconf.get('AMPLITUDE_KEY'); +const AMPLITUDE_SECRET = nconf.get('AMPLITUDE_SECRET'); +const BASE_URL = nconf.get('BASE_URL'); + +async function _deleteAmplitudeData (userId, email) { + const response = await axios.post( + 'https://amplitude.com/api/2/deletions/users', + { + user_ids: userId, // eslint-disable-line camelcase + requester: email, + }, + { + auth: { + username: AMPLITUDE_KEY, + password: AMPLITUDE_SECRET, + }, + } + ); + + console.log(`${response.status} ${response.statusText}`); +} + +async function _deleteHabiticaData (user) { + await User.update( + {_id: user._id}, + {$set: { + 'auth.local.passwordHashMethod': 'bcrypt', + 'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW', + }} + ); + const response = await axios.delete( + `${BASE_URL}/api/v3/user`, + { + data: { + password: 'test', + }, + headers: { + 'x-api-user': user._id, + 'x-api-key': user.apiToken, + }, + } + ); + + console.log(`${response.status} ${response.statusText}`); + if (response.status === 200) console.log(`${user._id} removed. Last login: ${user.auth.timestamps.loggedin}`); +} + +async function _processEmailAddress (email) { + const emailRegex = new RegExp(`^${email}`, 'i'); + const users = await User.find({ + $or: [ + {'auth.local.email': emailRegex}, + {'auth.facebook.emails.value': emailRegex}, + {'auth.google.emails.value': emailRegex}, + ]}, + { + _id: 1, + apiToken: 1, + auth: 1, + }).exec(); + + if (users.length < 1) { + console.warn(`No users found with email address ${email}`); + } else { + for (const user of users) { + await _deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop + await _deleteHabiticaData(user); // eslint-disable-line no-await-in-loop + } + } +} + +function deleteUserData (emails) { + const emailPromises = emails.map(_processEmailAddress); + return Promise.all(emailPromises); +} + +module.exports = deleteUserData; From 9f76db12bd0c2dfe8c056a2f269ecda3695c6f42 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Mon, 8 Oct 2018 10:55:41 +0200 Subject: [PATCH 04/13] update aws-sdk, in-app-purchase, fix #10733 and #10725 --- package-lock.json | 203 ++++++++++++++++------------------------------ package.json | 4 +- 2 files changed, 70 insertions(+), 137 deletions(-) diff --git a/package-lock.json b/package-lock.json index 24dffb4ff9..d5979644e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -277,6 +277,7 @@ "version": "3.0.1", "resolved": "http://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, "requires": { "acorn": "^3.0.4" }, @@ -284,7 +285,8 @@ "acorn": { "version": "3.3.0", "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true } } }, @@ -322,7 +324,8 @@ "ajv-keywords": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=" + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true }, "align-text": { "version": "0.1.4", @@ -1075,7 +1078,8 @@ "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true }, "asap": { "version": "2.0.6", @@ -1682,9 +1686,9 @@ } }, "aws-sdk": { - "version": "2.320.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.320.0.tgz", - "integrity": "sha512-qJBjZ0sIIy6AzBe0RkK5HDl3Kl1uAz01R4Nqy+RyflB//XWz+dPN8CFYtzrQNyfpLtPe/4uPrmxC4NwGW1MXBQ==", + "version": "2.329.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.329.0.tgz", + "integrity": "sha512-5yti9jyL6QL8CP5LGKX3uyrQcLAvLk6kyMZ749+F68IsutFKi0DaYxjxqx7lqWfzCSH3Z5REJ+XeywJLQ2tqfQ==", "requires": { "buffer": "4.9.1", "events": "1.1.1", @@ -5175,6 +5179,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, "requires": { "callsites": "^0.2.0" } @@ -5188,7 +5193,8 @@ "callsites": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=" + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true }, "camel-case": { "version": "3.0.0", @@ -5707,7 +5713,8 @@ "circular-json": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==" + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true }, "clap": { "version": "1.2.3", @@ -8104,7 +8111,8 @@ "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true }, "default-compare": { "version": "1.0.0", @@ -8246,6 +8254,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, "requires": { "globby": "^5.0.0", "is-path-cwd": "^1.0.0", @@ -8260,6 +8269,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, "requires": { "array-union": "^1.0.1", "arrify": "^1.0.0", @@ -8272,7 +8282,8 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true } } }, @@ -8380,6 +8391,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, "requires": { "esutils": "^2.0.2" } @@ -9733,6 +9745,7 @@ "version": "3.5.4", "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, "requires": { "acorn": "^5.5.0", "acorn-jsx": "^3.0.0" @@ -9747,6 +9760,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, "requires": { "estraverse": "^4.0.0" } @@ -10568,7 +10582,8 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true }, "fastparse": { "version": "1.1.1", @@ -10624,6 +10639,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, "requires": { "flat-cache": "^1.2.1", "object-assign": "^4.0.1" @@ -10952,6 +10968,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, "requires": { "circular-json": "^0.3.1", "del": "^2.0.2", @@ -11637,7 +11654,8 @@ "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true }, "gauge": { "version": "2.7.4", @@ -13298,7 +13316,8 @@ "ignore": { "version": "3.3.10", "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true }, "ignore-by-default": { "version": "1.0.1", @@ -13393,22 +13412,16 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "in-app-purchase": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/in-app-purchase/-/in-app-purchase-1.10.1.tgz", - "integrity": "sha512-pGWJ6YltJxVjYsk6ZISFROcm2E0FjGLF7FDoPaED1sedENqr560+1oVga2MbxgbxMnv8ufbSORUzQRYhx+ea6A==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/in-app-purchase/-/in-app-purchase-1.10.2.tgz", + "integrity": "sha512-NMyNJ0xaHB9F6fAgJ4VROzZRwFyX+iNYbniUCbd2OadDZdosUbPWRbFaP4QzyMRzGPa5sVLGm91LqIqk37UnDQ==", "requires": { - "eslint": "4.18.2", "jwt-simple": "^0.5.1", "request": "2.87.0", "xml-crypto": "0.10.1", "xmldom": "0.1.19" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -13424,73 +13437,6 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", - "requires": { - "ms": "^2.1.1" - } - }, - "eslint": { - "version": "4.18.2", - "resolved": "http://registry.npmjs.org/eslint/-/eslint-4.18.2.tgz", - "integrity": "sha512-qy4i3wODqKMYfz9LUI8N2qYDkHkoieTbiHpMrYUI/WbjhXJQr7lI4VngixTgaG+yHX+NBCv7nW4hA0ShbvaNKw==", - "requires": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", - "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.2", - "esquery": "^1.0.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", - "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", - "progress": "^2.0.0", - "require-uncached": "^1.0.3", - "semver": "^5.3.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, "form-data": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", @@ -13511,11 +13457,6 @@ } } }, - "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==" - }, "har-validator": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", @@ -13535,20 +13476,6 @@ "sshpk": "^1.7.0" } }, - "js-yaml": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -13581,19 +13508,6 @@ "uuid": "^3.1.0" } }, - "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -14293,7 +14207,8 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true }, "is-retry-allowed": { "version": "1.1.0", @@ -14724,7 +14639,8 @@ "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true }, "json-stringify-safe": { "version": "5.0.1", @@ -14840,9 +14756,9 @@ } }, "jwt-simple": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/jwt-simple/-/jwt-simple-0.5.3.tgz", - "integrity": "sha512-mGXp4PTJalEBh5WGe/Ow0P6g+Q4tM/9qAysX7YxCdB/xahDoBKrASZKf7hXT/qTREhOTPJvi4WWpabT2WXh1BQ==" + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/jwt-simple/-/jwt-simple-0.5.5.tgz", + "integrity": "sha512-KEyanRIDHooo8KuBxY3CC019NbwHtxdsxzRJUfaGqcxMrvBPBkosN+RUxx1nZFI6yrErq3KTW8HI/qrNIxHe0g==" }, "kareem": { "version": "2.1.0", @@ -15439,6 +15355,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, "requires": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -17613,7 +17530,8 @@ "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true }, "nconf": { "version": "0.10.0", @@ -18918,6 +18836,7 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, "requires": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.4", @@ -18930,7 +18849,8 @@ "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true } } }, @@ -20078,7 +19998,8 @@ "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==" + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true }, "pngjs": { "version": "3.3.3", @@ -21752,7 +21673,8 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true }, "prepend-http": { "version": "1.0.4", @@ -21833,7 +21755,8 @@ "progress": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=" + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true }, "project-name": { "version": "0.2.6", @@ -23159,6 +23082,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, "requires": { "caller-path": "^0.1.0", "resolve-from": "^1.0.0" @@ -23278,7 +23202,8 @@ "resolve-from": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=" + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true }, "resolve-glob": { "version": "1.0.0", @@ -23845,6 +23770,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0" }, @@ -23852,7 +23778,8 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true } } }, @@ -25052,6 +24979,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, "requires": { "ajv": "^5.2.3", "ajv-keywords": "^2.1.0", @@ -25064,17 +24992,20 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -25084,6 +25015,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -25862,6 +25794,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, "requires": { "prelude-ls": "~1.1.2" } diff --git a/package.json b/package.json index 16070db1ff..53869053d7 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "apidoc": "^0.17.5", "apn": "^2.2.0", "autoprefixer": "^8.5.0", - "aws-sdk": "^2.239.1", + "aws-sdk": "^2.329.0", "axios": "^0.18.0", "axios-progress-bar": "^1.2.0", "babel-core": "^6.26.3", @@ -53,7 +53,7 @@ "hellojs": "^1.15.1", "html-webpack-plugin": "^3.2.0", "image-size": "^0.6.2", - "in-app-purchase": "^1.9.4", + "in-app-purchase": "^1.10.2", "intro.js": "^2.9.3", "jquery": ">=3.0.0", "js2xmlparser": "^3.0.0", From cfc0f6a3ac399c545de7ec14aaf2e3d15eb5344c Mon Sep 17 00:00:00 2001 From: Kirsty Date: Mon, 8 Oct 2018 20:38:16 +0100 Subject: [PATCH 05/13] remove items with that have been lost from Class:None (#10735) --- website/common/script/libs/shops.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index dc21fe20ae..f781512e3b 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -188,8 +188,7 @@ shops.getMarketGearCategories = function getMarketGear (user, language) { }; let specialNonClassGear = filter(content.gear.flat, (gear) => { - return user.items.gear.owned[gear.key] === false || - !user.items.gear.owned[gear.key] && + return !user.items.gear.owned[gear.key] && content.classes.indexOf(gear.klass) === -1 && content.classes.indexOf(gear.specialClass) === -1 && (gear.canOwn && gear.canOwn(user)); From 67a8eebb96470a92357a607db3f5774f5d2587dd Mon Sep 17 00:00:00 2001 From: Kirsty Date: Mon, 8 Oct 2018 20:54:05 +0100 Subject: [PATCH 06/13] add errors for any param validation failures to the snackbar (#10724) --- website/client/app.vue | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/website/client/app.vue b/website/client/app.vue index 9f1680eeff..6583241956 100644 --- a/website/client/app.vue +++ b/website/client/app.vue @@ -329,23 +329,13 @@ export default { if (notificationNotFoundMessage.indexOf(errorMessage) !== -1) snackbarTimeout = true; let errorsToShow = []; - let usernameCheck = false; - let emailCheck = false; - let passwordCheck = false; // show only the first error for each param + let paramErrorsFound = {}; if (errorData.errors) { for (let e of errorData.errors) { - if (!usernameCheck && e.param === 'username') { + if (!paramErrorsFound[e.param]) { errorsToShow.push(e.message); - usernameCheck = true; - } - if (!emailCheck && e.param === 'email') { - errorsToShow.push(e.message); - emailCheck = true; - } - if (!passwordCheck && e.param === 'password') { - errorsToShow.push(e.message); - passwordCheck = true; + paramErrorsFound[e.param] = true; } } } else { From 464cd877364f5d5edf0ac05a05ad8448d6d1ff63 Mon Sep 17 00:00:00 2001 From: Kirsty Date: Mon, 8 Oct 2018 21:11:26 +0100 Subject: [PATCH 07/13] Decrease mana when removing stat points from int (#10713) * Decrease mana when removing stat points from int * Revert "Decrease mana when removing stat points from int" This reverts commit 5e25e1355267963793e1371ddb54186e18b54638. * add mana when stat updates are saved * don't allow users to deallocate saved stat points in the ui * use flag to determine whether to add mana points * add test for not adding mana points when flag is set * Revert "add test for not adding mana points when flag is set" This reverts commit 6e8ff36a793554768f9743100fadc77f363ea0f3. * Revert "use flag to determine whether to add mana points" This reverts commit 274e2d0d3345106517e6be167adceb43194c51c9. * Revert "add mana when stat updates are saved" This reverts commit 422bd4919116fe0523cdda5d1264f72ee08ed733. * move client side stat allocation to when save is pressed * update displayed total stats during editing * Fix lint errors --- .../components/userMenu/profileStats.vue | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/website/client/components/userMenu/profileStats.vue b/website/client/components/userMenu/profileStats.vue index fda5c1d658..1d06c5ebce 100644 --- a/website/client/components/userMenu/profileStats.vue +++ b/website/client/components/userMenu/profileStats.vue @@ -99,7 +99,7 @@ span.hint(:popover-title='$t(statInfo.title)', popover-placement='right', :popover='$t(statInfo.popover)', popover-trigger='mouseenter') .stat-title(:class='stat') {{ $t(statInfo.title) }} - strong.number {{ statsComputed[stat] | floorWholeNumber }} + strong.number {{totalStatPoints(stat) | floorWholeNumber}} .col-12.col-md-6 ul.bonus-stats li @@ -113,7 +113,7 @@ | {{statsComputed.classBonus[stat]}} li strong {{$t('allocated')}}: - | {{user.stats[stat]}} + | {{totalAllocatedStats(stat)}} li strong {{$t('buffs')}}: | {{user.stats.buffs[stat]}} @@ -124,7 +124,7 @@ h3 | {{$t('statPoints')}} .counter.badge(v-if='user.stats.points || userLevel100Plus') - | {{user.stats.points}}  + | {{pointsRemaining}}  .col-12.col-md-6 .float-right toggle-switch( @@ -137,7 +137,7 @@ .box.white.row.col-12 .col-9 div(:class='stat') {{ $t(stats[stat].title) }} - .number {{ user.stats[stat] }} + .number {{totalAllocatedStats(stat)}} .points {{$t('pts')}} .col-3 div @@ -157,7 +157,7 @@ import Content from '../../../common/script/content'; import { beastMasterProgress, mountMasterProgress } from '../../../common/script/count'; import autoAllocate from '../../../common/script/fns/autoAllocate'; - import allocate from '../../../common/script/ops/stats/allocate'; + import allocateBulk from '../../../common/script/ops/stats/allocateBulk'; import statsComputed from '../../../common/script/libs/statsComputed'; import axios from 'axios'; @@ -239,14 +239,27 @@ return this.user.stats.lvl >= 100; }, showStatsSave () { - const statsAreBeingUpdated = Object.values(this.statUpdates).find(stat => stat > 0); - return Boolean(this.user.stats.points) || statsAreBeingUpdated; + return Boolean(this.user.stats.points); }, + pointsRemaining () { + let points = this.user.stats.points; + Object.values(this.statUpdates).forEach(value => { + points -= value; + }); + return points; + }, + }, methods: { getGearTitle (key) { return this.flatGear[key].text(); }, + totalAllocatedStats (stat) { + return this.user.stats[stat] + this.statUpdates[stat]; + }, + totalStatPoints (stat) { + return this.statsComputed[stat] + this.statUpdates[stat]; + }, totalCount (objectToCount) { let total = size(objectToCount); return total; @@ -292,14 +305,12 @@ return display; }, allocate (stat) { - allocate(this.user, {query: { stat }}); - this.statUpdates[stat] += 1; + if (this.pointsRemaining === 0) return; + this.statUpdates[stat]++; }, deallocate (stat) { - if (this.user.stats[stat] === 0) return; - this.user.stats[stat] -= 1; - this.user.stats.points += 1; - this.statUpdates[stat] -= 1; + if (this.statUpdates[stat] === 0) return; + this.statUpdates[stat]--; }, async saveAttributes () { this.loading = true; @@ -309,10 +320,7 @@ if (this.statUpdates[stat] > 0) statUpdates[stat] = this.statUpdates[stat]; }); - await axios.post('/api/v4/user/allocate-bulk', { - stats: statUpdates, - }); - + // reset statUpdates to zero before request to avoid display errors while waiting for server this.statUpdates = { str: 0, int: 0, @@ -320,6 +328,12 @@ per: 0, }; + allocateBulk(this.user, { body: { stats: statUpdates } }); + + await axios.post('/api/v4/user/allocate-bulk', { + stats: statUpdates, + }); + this.loading = false; }, allocateNow () { From bdfc23717e71380bdf07878ec6632ca95908859f Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Tue, 9 Oct 2018 03:16:14 +0700 Subject: [PATCH 08/13] Css font home page update (#10672) * css font home page update finish home page font change * Small fixes on css font update on home page --- website/client/components/static/header.vue | 8 ++++++-- website/client/components/static/home.vue | 21 +++++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/website/client/components/static/header.vue b/website/client/components/static/header.vue index 667172b646..9d17a17fb0 100644 --- a/website/client/components/static/header.vue +++ b/website/client/components/static/header.vue @@ -42,6 +42,11 @@ } } + .home-header, .home-header .btn { + font-family: 'Varela Round', sans-serif; + font-weight: normal; + } + .btn-primary.pull-right { height: 2.5em; margin: auto 0px auto auto; @@ -66,9 +71,8 @@ .nav-item { .nav-link { - font-size: 16px; + font-size: 16px !important; color: $white; - font-weight: bold; line-height: 1.5; padding: 16px 24px; transition: none; diff --git a/website/client/components/static/home.vue b/website/client/components/static/home.vue index 57c7ec06aa..fa1e604317 100644 --- a/website/client/components/static/home.vue +++ b/website/client/components/static/home.vue @@ -126,6 +126,8 @@ From 97021e3422f231649a79fedb23d457a61a65acc9 Mon Sep 17 00:00:00 2001 From: titchimoto Date: Mon, 8 Oct 2018 13:22:09 -0700 Subject: [PATCH 10/13] Issue 10414 - Remove Member Option from View Party link. (#10639) * Add remove member option from main task page * Code refactor for remove member options * code refactor to avoid loading party multiple times * fix dispatch to ensure only pulling once from server --- website/client/components/header/index.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/website/client/components/header/index.vue b/website/client/components/header/index.vue index 9c28765893..dffdef7046 100644 --- a/website/client/components/header/index.vue +++ b/website/client/components/header/index.vue @@ -154,11 +154,13 @@ export default { this.$root.$emit('bv::show::modal', 'create-party-modal'); } }, - showPartyMembers () { + async showPartyMembers () { + const party = await this.$store.dispatch('party:getParty'); + this.$root.$emit('habitica:show-member-modal', { - groupId: this.user.party._id, + groupId: party.data._id, viewingMembers: this.partyMembers, - group: this.user.party, + group: party.data, }); }, setPartyMembersWidth ($event) { From e7969987ecd5a27c869f2d00e6f4da99cb04e719 Mon Sep 17 00:00:00 2001 From: Robert Kojima Date: Mon, 8 Oct 2018 13:25:49 -0700 Subject: [PATCH 11/13] Guild textarea at list positioning (#10663) * autocomplete dialog now has ternary operator to determine placement * added min height to textbox * fixed spacing according to travisCI * heightToUse function now retrieves argument from props --- website/client/components/chat/autoComplete.vue | 11 +++++++++-- website/client/components/groups/chat.vue | 5 ++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/website/client/components/chat/autoComplete.vue b/website/client/components/chat/autoComplete.vue index dca7480ff3..8b5cc3cf43 100644 --- a/website/client/components/chat/autoComplete.vue +++ b/website/client/components/chat/autoComplete.vue @@ -14,7 +14,7 @@ div.autocomplete-selection(v-if='searchResults.length > 0', :style='autocomplete import groupBy from 'lodash/groupBy'; export default { - props: ['selections', 'text', 'coords', 'chat'], + props: ['selections', 'text', 'coords', 'chat', 'textbox'], data () { return { currentSearch: '', @@ -25,9 +25,15 @@ export default { }, computed: { autocompleteStyle () { + function heightToUse (textBox, topCoords) { + let textBoxHeight = textBox['user-entry'].clientHeight; + return topCoords < textBoxHeight ? topCoords + 30 : textBoxHeight + 10; + } return { - top: `${this.coords.TOP + 30}px`, + top: `${heightToUse(this.textbox, this.coords.TOP)}px`, left: `${this.coords.LEFT + 30}px`, + marginLeft: '-28px', + marginTop: '28px', position: 'absolute', minWidth: '100px', minHeight: '100px', @@ -42,6 +48,7 @@ export default { return option.toLowerCase().indexOf(currentSearch.toLowerCase()) !== -1; }); }, + }, mounted () { this.grabUserNames(); diff --git a/website/client/components/groups/chat.vue b/website/client/components/groups/chat.vue index 5babe873f9..9c89526fbf 100644 --- a/website/client/components/groups/chat.vue +++ b/website/client/components/groups/chat.vue @@ -6,6 +6,7 @@ .row textarea(:placeholder='placeholder', v-model='newMessage', + ref='user-entry', :class='{"user-entry": newMessage}', @keydown='updateCarretPosition', @keyup.ctrl.enter='sendMessageShortcut()', @@ -16,6 +17,7 @@ autocomplete( :text='newMessage', v-on:select="selectedAutocomplete", + :textbox='textbox', :coords='coords', :chat='group.chat') @@ -62,6 +64,7 @@ TOP: 0, LEFT: 0, }, + textbox: this.$refs, }; }, computed: { @@ -187,7 +190,7 @@ position: relative; textarea { - height: 150px; + min-height: 150px; width: 100%; background-color: $white; border: solid 1px $gray-400; From 28ed9d8bcccb405fc23d83251c0d3617c1fabf41 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 9 Oct 2018 20:27:52 +0000 Subject: [PATCH 12/13] fix(script): log, not warn, so all output goes to both stdout and tee --- scripts/gdpr-delete-users.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gdpr-delete-users.js b/scripts/gdpr-delete-users.js index 59dd5f5cc2..d5942dace4 100644 --- a/scripts/gdpr-delete-users.js +++ b/scripts/gdpr-delete-users.js @@ -65,7 +65,7 @@ async function _processEmailAddress (email) { }).exec(); if (users.length < 1) { - console.warn(`No users found with email address ${email}`); + console.log(`No users found with email address ${email}`); } else { for (const user of users) { await _deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop From 67bb179c257b30171df1b91fbb55cfb85de73b6d Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Thu, 11 Oct 2018 19:01:15 +0200 Subject: [PATCH 13/13] gifts: prevent users from sending the same gift twice by clicking many times on the Send button --- website/client/components/payments/sendGemsModal.vue | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/website/client/components/payments/sendGemsModal.vue b/website/client/components/payments/sendGemsModal.vue index 98d32993c9..791ebe0ae5 100644 --- a/website/client/components/payments/sendGemsModal.vue +++ b/website/client/components/payments/sendGemsModal.vue @@ -41,7 +41,11 @@ b-modal#send-gems(:title="title", :hide-footer="true", size='lg', @hide='onHide( //include ../formatting-help .modal-footer - button.btn.btn-primary(v-if='fromBal', @click='sendGift()') {{ $t("send") }} + button.btn.btn-primary( + v-if="fromBal", + @click="sendGift()", + :disabled="sendingInProgress" + ) {{ $t("send") }} template(v-else) button.btn.btn-primary(@click='showStripe({gift, uuid: userReceivingGems._id})') {{ $t('card') }} button.btn.btn-warning(@click='openPaypalGift({gift: gift, giftedTo: userReceivingGems._id})') PayPal @@ -103,6 +107,7 @@ export default { assistanceEmailObject: { hrefTechAssistanceEmail: `${TECH_ASSISTANCE_EMAIL}`, }, + sendingInProgress: false, }; }, computed: { @@ -130,6 +135,7 @@ export default { methods: { // @TODO move to payments mixin or action (problem is that we need notifications) async sendGift () { + this.sendingInProgress = true; await this.$store.dispatch('members:transferGems', { message: this.gift.message, toUserId: this.userReceivingGems._id, @@ -139,7 +145,9 @@ export default { this.close(); }, onHide () { + this.gift.gems.amount = 0; this.gift.message = ''; + this.sendingInProgress = false; }, close () { this.$root.$emit('bv::hide::modal', 'send-gems');