From 3d39718048c14703c6fe6418c08b9f587ecc4a41 Mon Sep 17 00:00:00 2001 From: negue Date: Fri, 18 May 2018 17:00:39 +0200 Subject: [PATCH 01/23] Purchase API Refactoring: Spells [Gold] (#10305) * convert buySpell operation * remove purchaseWithSpell - change purchaseType 'special' to 'spells' - fix lint * fix tests * rollback 'spells' to 'special' --- .../buy/{buySpecialSpell.js => buySpell.js} | 7 ++- website/client/store/actions/shops.js | 3 +- website/common/script/ops/buy/buy.js | 9 ++-- website/common/script/ops/buy/buyQuest.js | 13 ++--- .../common/script/ops/buy/buySpecialSpell.js | 48 ------------------- website/common/script/ops/buy/buySpell.js | 47 ++++++++++++++++++ .../script/ops/buy/purchaseWithSpell.js | 12 ----- 7 files changed, 64 insertions(+), 75 deletions(-) rename test/common/ops/buy/{buySpecialSpell.js => buySpell.js} (90%) delete mode 100644 website/common/script/ops/buy/buySpecialSpell.js create mode 100644 website/common/script/ops/buy/buySpell.js delete mode 100644 website/common/script/ops/buy/purchaseWithSpell.js diff --git a/test/common/ops/buy/buySpecialSpell.js b/test/common/ops/buy/buySpell.js similarity index 90% rename from test/common/ops/buy/buySpecialSpell.js rename to test/common/ops/buy/buySpell.js index bd4b0cf22c..02d0bda6a4 100644 --- a/test/common/ops/buy/buySpecialSpell.js +++ b/test/common/ops/buy/buySpell.js @@ -1,4 +1,4 @@ -import buySpecialSpell from '../../../../website/common/script/ops/buy/buySpecialSpell'; +import {BuySpellOperation} from '../../../../website/common/script/ops/buy/buySpell'; import { BadRequest, NotFound, @@ -15,6 +15,11 @@ describe('shared.ops.buySpecialSpell', () => { let user; let analytics = {track () {}}; + function buySpecialSpell (_user, _req, _analytics) { + const buyOp = new BuySpellOperation(_user, _req, _analytics); + + return buyOp.purchase(); + } beforeEach(() => { user = generateUser(); sinon.stub(analytics, 'track'); diff --git a/website/client/store/actions/shops.js b/website/client/store/actions/shops.js index 56bbf4ac9b..1900533e1a 100644 --- a/website/client/store/actions/shops.js +++ b/website/client/store/actions/shops.js @@ -1,7 +1,6 @@ import axios from 'axios'; import buyOp from 'common/script/ops/buy/buy'; import content from 'common/script/content/index'; -import purchaseOp from 'common/script/ops/buy/purchaseWithSpell'; import hourglassPurchaseOp from 'common/script/ops/buy/hourglassPurchase'; import sellOp from 'common/script/ops/sell'; import unlockOp from 'common/script/ops/unlock'; @@ -91,7 +90,7 @@ async function buyArmoire (store, params) { export function purchase (store, params) { const quantity = params.quantity || 1; const user = store.state.user.data; - let opResult = purchaseOp(user, {params, quantity}); + let opResult = buyOp(user, {params, quantity}); return { result: opResult, diff --git a/website/common/script/ops/buy/buy.js b/website/common/script/ops/buy/buy.js index e3cb62bdca..9bcee0706e 100644 --- a/website/common/script/ops/buy/buy.js +++ b/website/common/script/ops/buy/buy.js @@ -7,7 +7,7 @@ import {BuyHealthPotionOperation} from './buyHealthPotion'; import {BuyMarketGearOperation} from './buyMarketGear'; import buyMysterySet from './buyMysterySet'; import {BuyQuestWithGoldOperation} from './buyQuest'; -import buySpecialSpell from './buySpecialSpell'; +import {BuySpellOperation} from './buySpell'; import purchaseOp from './purchase'; import hourglassPurchase from './hourglassPurchase'; import errorMessage from '../../libs/errorMessage'; @@ -70,9 +70,12 @@ module.exports = function buy (user, req = {}, analytics) { buyRes = buyOp.purchase(); break; } - case 'special': - buyRes = buySpecialSpell(user, req, analytics); + case 'special': { + const buyOp = new BuySpellOperation(user, req, analytics); + + buyRes = buyOp.purchase(); break; + } default: { const buyOp = new BuyMarketGearOperation(user, req, analytics); diff --git a/website/common/script/ops/buy/buyQuest.js b/website/common/script/ops/buy/buyQuest.js index b7e9f783b9..1fd60949fd 100644 --- a/website/common/script/ops/buy/buyQuest.js +++ b/website/common/script/ops/buy/buyQuest.js @@ -25,6 +25,10 @@ export class BuyQuestWithGoldOperation extends AbstractGoldItemOperation { user.achievements.quests.taskwoodsTerror3; } + getItemKey () { + return this.key; + } + getItemValue (item) { return item.goldValue; } @@ -61,13 +65,4 @@ export class BuyQuestWithGoldOperation extends AbstractGoldItemOperation { }), ]; } - - analyticsData () { - return { - itemKey: this.key, - itemType: 'Market', - acquireMethod: 'Gold', - goldCost: this.getItemValue(this.item.goldValue), - }; - } } diff --git a/website/common/script/ops/buy/buySpecialSpell.js b/website/common/script/ops/buy/buySpecialSpell.js deleted file mode 100644 index d63638acfd..0000000000 --- a/website/common/script/ops/buy/buySpecialSpell.js +++ /dev/null @@ -1,48 +0,0 @@ -import i18n from '../../i18n'; -import content from '../../content/index'; -import get from 'lodash/get'; -import pick from 'lodash/pick'; -import splitWhitespace from '../../libs/splitWhitespace'; -import { - BadRequest, - NotAuthorized, - NotFound, -} from '../../libs/errors'; -import errorMessage from '../../libs/errorMessage'; - -module.exports = function buySpecialSpell (user, req = {}, analytics) { - let key = get(req, 'params.key'); - let quantity = req.quantity || 1; - - if (!key) throw new BadRequest(errorMessage('missingKeyParam')); - - let item = content.special[key]; - if (!item) throw new NotFound(errorMessage('spellNotFound', {spellId: key})); - - if (user.stats.gp < item.value * quantity) { - throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); - } - user.stats.gp -= item.value * quantity; - - user.items.special[key] += quantity; - - if (analytics) { - analytics.track('acquire item', { - uuid: user._id, - itemKey: item.key, - itemType: 'Market', - goldCost: item.goldValue, - quantityPurchased: quantity, - acquireMethod: 'Gold', - category: 'behavior', - headers: req.headers, - }); - } - - return [ - pick(user, splitWhitespace('items stats')), - i18n.t('messageBought', { - itemText: item.text(req.language), - }, req.language), - ]; -}; diff --git a/website/common/script/ops/buy/buySpell.js b/website/common/script/ops/buy/buySpell.js new file mode 100644 index 0000000000..2a26d0defb --- /dev/null +++ b/website/common/script/ops/buy/buySpell.js @@ -0,0 +1,47 @@ +import content from '../../content/index'; +import get from 'lodash/get'; +import pick from 'lodash/pick'; +import splitWhitespace from '../../libs/splitWhitespace'; +import { + BadRequest, + NotFound, +} from '../../libs/errors'; +import {AbstractGoldItemOperation} from './abstractBuyOperation'; +import errorMessage from '../../libs/errorMessage'; + +export class BuySpellOperation extends AbstractGoldItemOperation { + constructor (user, req, analytics) { + super(user, req, analytics); + } + + getItemKey () { + return this.key; + } + + multiplePurchaseAllowed () { + return true; + } + + extractAndValidateParams (user, req) { + let key = this.key = get(req, 'params.key'); + if (!key) throw new BadRequest(errorMessage('missingKeyParam')); + + let item = content.special[key]; + if (!item) throw new NotFound(errorMessage('spellNotFound', {spellId: key})); + + this.canUserPurchase(user, item); + } + + executeChanges (user, item, req) { + user.items.special[item.key] += this.quantity; + + this.subtractCurrency(user, item, this.quantity); + + return [ + pick(user, splitWhitespace('items stats')), + this.i18n('messageBought', { + itemText: item.text(req.language), + }), + ]; + } +} diff --git a/website/common/script/ops/buy/purchaseWithSpell.js b/website/common/script/ops/buy/purchaseWithSpell.js deleted file mode 100644 index 7f0a087190..0000000000 --- a/website/common/script/ops/buy/purchaseWithSpell.js +++ /dev/null @@ -1,12 +0,0 @@ -import buy from './buy'; -import get from 'lodash/get'; - -module.exports = function purchaseWithSpell (user, req = {}, analytics) { - const type = get(req.params, 'type'); - - if (type === 'spells') { - req.type = 'special'; - } - - return buy(user, req, analytics); -}; From de9883c3acee50d35bca30b06fabf441ae0cd270 Mon Sep 17 00:00:00 2001 From: negue Date: Fri, 18 May 2018 17:01:05 +0200 Subject: [PATCH 02/23] extract chat (#10362) * extract chatTextarea from group/tavern - extract staffList array * fix lint / rewrite condition * clean up - part 1 * rename chatTextarea to chat * refactor timestamp check --- website/client/components/groups/chat.vue | 222 +++++++++++++++ website/client/components/groups/group.vue | 113 +------- website/client/components/groups/tavern.vue | 282 ++------------------ website/client/libs/staffList.js | 97 +++++++ website/server/models/group.js | 8 +- 5 files changed, 349 insertions(+), 373 deletions(-) create mode 100644 website/client/components/groups/chat.vue create mode 100644 website/client/libs/staffList.js diff --git a/website/client/components/groups/chat.vue b/website/client/components/groups/chat.vue new file mode 100644 index 0000000000..2ba54afb4e --- /dev/null +++ b/website/client/components/groups/chat.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/website/client/components/groups/group.vue b/website/client/components/groups/group.vue index 3f61a5033f..28436ff084 100644 --- a/website/client/components/groups/group.vue +++ b/website/client/components/groups/group.vue @@ -27,25 +27,16 @@ .svg-icon.gem(v-html="icons.gem") span.number {{group.balance * 4}} div(v-once) {{ $t('guildBank') }} - .row.chat-row - .col-12 - h3(v-once) {{ $t('chat') }} - .row.new-message-row - textarea(:placeholder="!isParty ? $t('chatPlaceholder') : $t('partyChatPlaceholder')", v-model='newMessage', @keydown='updateCarretPosition', @keyup.ctrl.enter='sendMessageShortcut()', @paste='disableMessageSendShortcut()') - autocomplete(:text='newMessage', v-on:select="selectedAutocomplete", :coords='coords', :chat='group.chat') - .row.chat-actions - .col-6.chat-receive-actions - button.btn.btn-secondary.float-left.fetch(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }} - button.btn.btn-secondary.float-left(v-once, @click='reverseChat()') {{ $t('reverseChat') }} - .col-6.chat-send-actions - button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }} - community-guidelines + chat( + :label="$t('chat')", + :group="group", + :placeholder="!isParty ? $t('chatPlaceholder') : $t('partyChatPlaceholder')", + @fetchRecentMessages="fetchRecentMessages()" + ) + template(slot="additionRow") .row(v-if='showNoNotificationsMessage') .col-12.no-notifications | {{$t('groupNoNotifications')}} - .row - .col-12.hr - chat-message(:chat.sync='group.chat', :group-id='group._id', :group-name='group.name') .col-12.col-sm-4.sidebar .row(:class='{"guild-background": !isParty}') .col-12 @@ -260,7 +251,6 @@ diff --git a/website/client/libs/staffList.js b/website/client/libs/staffList.js new file mode 100644 index 0000000000..96eb262fc3 --- /dev/null +++ b/website/client/libs/staffList.js @@ -0,0 +1,97 @@ +export default [ + { + name: 'beffymaroo', + type: 'Staff', + uuid: '9fe7183a-4b79-4c15-9629-a1aee3873390', + }, + // { + // name: 'lefnire', + // type: 'Staff', + // uuid: '00000000-0000-4000-9000-000000000000', + // }, + { + name: 'Lemoness', + type: 'Staff', + uuid: '7bde7864-ebc5-4ee2-a4b7-1070d464cdb0', + }, + { + name: 'paglias', + type: 'Staff', + uuid: 'ed4c688c-6652-4a92-9d03-a5a79844174a', + }, + { + name: 'redphoenix', + type: 'Staff', + uuid: 'cb46ad54-8c78-4dbc-a8ed-4e3185b2b3ff', + }, + { + name: 'SabreCat', + type: 'Staff', + uuid: '7f14ed62-5408-4e1b-be83-ada62d504931', + }, + { + name: 'TheHollidayInn', + type: 'Staff', + uuid: '206039c6-24e4-4b9f-8a31-61cbb9aa3f66', + }, + { + name: 'viirus', + type: 'Staff', + uuid: 'a327d7e0-1c2e-41be-9193-7b30b484413f', + }, + { + name: 'It\'s Bailey', + type: 'Moderator', + uuid: '9da65443-ed43-4c21-804f-d260c1361596', + }, + { + name: 'Alys', + type: 'Moderator', + uuid: 'd904bd62-da08-416b-a816-ba797c9ee265', + }, + { + name: 'Blade', + type: 'Moderator', + uuid: '75f270e8-c5db-4722-a5e6-a83f1b23f76b', + }, + { + name: 'Breadstrings', + type: 'Moderator', + uuid: '3b675c0e-d7a6-440c-8687-bc67cd0bf4e9', + }, + { + name: 'Cantras', + type: 'Moderator', + uuid: '28771972-ca6d-4c03-8261-e1734aa7d21d', + }, + // { + // name: 'Daniel the Bard', + // type: 'Moderator', + // uuid: '1f7c4a74-03a3-4b2c-b015-112d0acbd593', + // }, + { + name: 'deilann 5.0.5b', + type: 'Moderator', + uuid: 'e7b5d1e2-3b6e-4192-b867-8bafdb03eeec', + }, + { + name: 'Dewines', + type: 'Moderator', + uuid: '262a7afb-6b57-4d81-88e0-80d2e9f6cbdc', + }, + { + name: 'Fox_town', + type: 'Moderator', + uuid: 'a05f0152-d66b-4ef1-93ac-4adb195d0031', + }, + { + name: 'Megan', + type: 'Moderator', + uuid: '73e5125c-2c87-4004-8ccd-972aeac4f17a', + }, + { + name: 'shanaqui', + type: 'Moderator', + uuid: 'bb089388-28ae-4e42-a8fa-f0c2bfb6f779', + }, +]; diff --git a/website/server/models/group.js b/website/server/models/group.js index 8d98a1b1e7..65b49aee45 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -346,7 +346,11 @@ schema.statics.toJSONCleanChat = async function groupToJSONCleanChat (group, use // Convert to timestamps because Android expects it toJSON.chat.forEach(chat => { - chat.timestamp = chat.timestamp ? chat.timestamp.getTime() : new Date().getTime(); + // old chats are saved with a numeric timestamp + // new chats use `Date` which then has to be converted to the numeric timestamp + if (chat.timestamp.getTime) { + chat.timestamp = chat.timestamp.getTime(); + } }); return toJSON; @@ -1537,4 +1541,4 @@ if (!nconf.get('IS_TEST')) { privacy: 'public', }).save(); }); -} \ No newline at end of file +} From d3a0348ac7d69b9ed0f09255ecf63b650f1e5aab Mon Sep 17 00:00:00 2001 From: aszlig Date: Fri, 18 May 2018 17:01:27 +0200 Subject: [PATCH 03/23] Avoid using media element with empty src attribute (#10364) Whenever the client starts up, the following is emitted in the Firefox console: Invalid URI. Load of media resource failed. All candidate resources failed to load. Media load paused. This happens because the tags are preinitialized with a src attribute of "". So what we're doing instead is initialize the