From 4c62a48f5d7a38b9c38c1f41be36c2fa33f20f7e Mon Sep 17 00:00:00 2001 From: CuriousMagpie Date: Tue, 8 Nov 2022 16:32:26 -0500 Subject: [PATCH 1/9] chore(typo): who knew, that Y was actually important... --- website/common/locales/en/backgrounds.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/common/locales/en/backgrounds.json b/website/common/locales/en/backgrounds.json index 4e7a443e3a..70ddfb8ce3 100644 --- a/website/common/locales/en/backgrounds.json +++ b/website/common/locales/en/backgrounds.json @@ -830,8 +830,8 @@ "backgrounds112022": "SET 102: Released November 2022", "backgroundAmongGiantMushroomsText": "Among Giant Mushrooms", "backgroundAmongGiantMushroomsNotes": "Marvel at Giant Mushrooms.", - "backgroundMistAutumnForestText": "Misty Autumn Forest", - "backgroundMistAutumnForestNotes": "Wander through a Misty Autumn Forest.", + "backgroundMistyAutumnForestText": "Misty Autumn Forest", + "backgroundMistyAutumnForestNotes": "Wander through a Misty Autumn Forest.", "backgroundAutumnBridgeText": "Bridge in Autumn", "backgroundAutumnBridgeNotes": "Admire the beauty of a Bridge in Autumn.", From dbd485cb96139d91dbdb87a2868cb69bfccf0c43 Mon Sep 17 00:00:00 2001 From: SabreCat Date: Wed, 9 Nov 2022 16:11:49 -0600 Subject: [PATCH 2/9] 4.249.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index da44048032..0048ab0587 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "habitica", - "version": "4.249.0", + "version": "4.249.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8ac9c82b67..ea29431c7d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "4.249.0", + "version": "4.249.1", "main": "./website/server/index.js", "dependencies": { "@babel/core": "^7.19.6", From 9f52e470119565ba3843c083ccc803d9b1d2f079 Mon Sep 17 00:00:00 2001 From: SabreCat Date: Mon, 14 Nov 2022 14:13:08 -0600 Subject: [PATCH 3/9] feat(content): November Quests and Hatching Potions by @CuriousMagpie --- website/common/locales/en/questsContent.json | 2 +- website/common/script/content/bundles.js | 3 +- .../common/script/content/constants/events.js | 14 +++++++++- .../common/script/content/hatching-potions.js | 19 +++++++------ .../script/content/shop-featuredItems.js | 28 +++++++++---------- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/website/common/locales/en/questsContent.json b/website/common/locales/en/questsContent.json index 99143ddf3c..440a2910b1 100644 --- a/website/common/locales/en/questsContent.json +++ b/website/common/locales/en/questsContent.json @@ -777,7 +777,7 @@ "questRobotUnlockText": "Unlocks purchasable Robot Eggs in the Market", "rockingReptilesText": "Rocking Reptiles Quest Bundle", - "rockingReptilesNotes": "Contains 'The Insta-Gator,' 'The Serpent of Distraction,' and 'The Veloci-Rapper.' Available until September 30.", + "rockingReptilesNotes": "Contains 'The Insta-Gator,' 'The Serpent of Distraction,' and 'The Veloci-Rapper.' Available until November 30.", "delightfulDinosText": "Delightful Dinos Quest Bundle", "delightfulDinosNotes": "Contains 'The Pterror-dactyl,' 'The Trampling Triceratops,' and 'The Dinosaur Unearthed.' Available until May 31.", diff --git a/website/common/script/content/bundles.js b/website/common/script/content/bundles.js index bcbe083860..cb4e982fee 100644 --- a/website/common/script/content/bundles.js +++ b/website/common/script/content/bundles.js @@ -208,8 +208,9 @@ const bundles = { 'snake', 'velociraptor', ], + event: EVENTS.bundle202211, canBuy () { - return moment().isBetween('2019-09-10', '2019-10-02'); + return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end); }, type: 'quests', value: 7, diff --git a/website/common/script/content/constants/events.js b/website/common/script/content/constants/events.js index e87b52fb7d..24db5d502e 100644 --- a/website/common/script/content/constants/events.js +++ b/website/common/script/content/constants/events.js @@ -9,9 +9,21 @@ const gemsPromo = { }; export const EVENTS = { + noEvent: { + start: '2022-11-30T20:00-04:00', + end: '2022-12-20T08:00-04:00', + season: 'normal', + npcImageSuffix: '', + }, + bundle202211: { + start: '2022-11-15T20:00-04:00', + end: '2022-11-30T08:00-04:00', + season: 'normal', + npcImageSuffix: '', + }, afterGala: { start: '2022-10-31T20:00-04:00', - end: '2022-12-21T08:00-04:00', + end: '2022-11-15T08:00-04:00', season: 'normal', npcImageSuffix: '', }, diff --git a/website/common/script/content/hatching-potions.js b/website/common/script/content/hatching-potions.js index 23f26f370d..a2e40396c2 100644 --- a/website/common/script/content/hatching-potions.js +++ b/website/common/script/content/hatching-potions.js @@ -122,26 +122,26 @@ const premium = { value: 2, text: t('hatchingPotionEmber'), limited: true, - event: EVENTS.potions202111, + event: EVENTS.bundle202211, _addlNotes: t('eventAvailabilityReturning', { availableDate: t('dateEndNovember'), - previousDate: t('novemberYYYY', { year: 2019 }), + previousDate: t('novemberYYYY', { year: 2021 }), }), canBuy () { - return moment().isBefore(EVENTS.potions202111.end); + return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end); }, }, Thunderstorm: { value: 2, text: t('hatchingPotionThunderstorm'), limited: true, - event: EVENTS.potions202108, + event: EVENTS.bundle202211, _addlNotes: t('eventAvailabilityReturning', { - availableDate: t('dateEndAugust'), - previousDate: t('novemberYYYY', { year: 2019 }), + availableDate: t('dateEndNovember'), + previousDate: t('novemberYYYY', { year: 2021 }), }), canBuy () { - return moment().isBetween(EVENTS.potions202108.start, EVENTS.potions202108.end); + return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end); }, }, Spooky: { @@ -251,12 +251,13 @@ const premium = { value: 2, text: t('hatchingPotionFrost'), limited: true, + event: EVENTS.bundle202211, _addlNotes: t('eventAvailabilityReturning', { availableDate: t('dateEndNovember'), - previousDate: t('novemberYYYY', { year: 2018 }), + previousDate: t('novemberYYYY', { year: 2020 }), }), canBuy () { - return moment().isBefore('2020-12-02'); + return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end); }, }, IcySnow: { diff --git a/website/common/script/content/shop-featuredItems.js b/website/common/script/content/shop-featuredItems.js index ea7e7da54f..031d7d3a9c 100644 --- a/website/common/script/content/shop-featuredItems.js +++ b/website/common/script/content/shop-featuredItems.js @@ -5,7 +5,7 @@ import { EVENTS } from './constants'; // path: 'premiumHatchingPotions.Rainbow', const featuredItems = { market () { - if (moment().isBetween(EVENTS.fall2022.start, EVENTS.fall2022.end)) { + if (moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end)) { return [ { type: 'armoire', @@ -13,15 +13,15 @@ const featuredItems = { }, { type: 'premiumHatchingPotion', - path: 'premiumHatchingPotions.Vampire', + path: 'premiumHatchingPotions.Frost', }, { type: 'premiumHatchingPotion', - path: 'premiumHatchingPotions.Ghost', + path: 'premiumHatchingPotions.Ember', }, { type: 'premiumHatchingPotion', - path: 'premiumHatchingPotions.Shadow', + path: 'premiumHatchingPotions.Thunderstorm', }, ]; } @@ -32,47 +32,47 @@ const featuredItems = { }, { type: 'food', - path: 'food.Potatoe', + path: 'food.Milk', }, { type: 'hatchingPotions', - path: 'hatchingPotions.Desert', + path: 'hatchingPotions.White', }, { type: 'eggs', - path: 'eggs.Dragon', + path: 'eggs.Fox', }, ]; }, quests () { - if (moment().isBetween(EVENTS.bundle202210.start, EVENTS.bundle202210.end)) { + if (moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end)) { return [ { type: 'bundles', - path: 'bundles.witchyFamiliars', + path: 'bundles.rockingReptiles', }, { type: 'quests', - path: 'quests.snake', + path: 'quests.peacock', }, { type: 'quests', - path: 'quests.owl', + path: 'quests.harpy', }, ]; } return [ { type: 'quests', - path: 'quests.guineapig', + path: 'quests.axolotl', }, { type: 'quests', - path: 'quests.onyx', + path: 'quests.stone', }, { type: 'quests', - path: 'quests.rooster', + path: 'quests.whale', }, ]; }, From 2bbff36cc834ccb2e3899c1fd95e90b227a1a015 Mon Sep 17 00:00:00 2001 From: SabreCat Date: Mon, 14 Nov 2022 14:13:13 -0600 Subject: [PATCH 4/9] 4.249.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0048ab0587..c292cb2cbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "habitica", - "version": "4.249.1", + "version": "4.249.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ea29431c7d..c7270f2b0d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "4.249.1", + "version": "4.249.2", "main": "./website/server/index.js", "dependencies": { "@babel/core": "^7.19.6", From 81540ef3991e7a886f3b2452f560e1a7f9fcf6e9 Mon Sep 17 00:00:00 2001 From: SabreCat Date: Mon, 14 Nov 2022 14:38:58 -0600 Subject: [PATCH 5/9] fix(events): EST now not EDT --- website/common/script/content/constants/events.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/website/common/script/content/constants/events.js b/website/common/script/content/constants/events.js index 24db5d502e..8e54486499 100644 --- a/website/common/script/content/constants/events.js +++ b/website/common/script/content/constants/events.js @@ -10,20 +10,20 @@ const gemsPromo = { export const EVENTS = { noEvent: { - start: '2022-11-30T20:00-04:00', - end: '2022-12-20T08:00-04:00', + start: '2022-11-30T20:00-05:00', + end: '2022-12-20T08:00-05:00', season: 'normal', npcImageSuffix: '', }, bundle202211: { - start: '2022-11-15T20:00-04:00', - end: '2022-11-30T08:00-04:00', + start: '2022-11-15T20:00-05:00', + end: '2022-11-30T08:00-05:00', season: 'normal', npcImageSuffix: '', }, afterGala: { start: '2022-10-31T20:00-04:00', - end: '2022-11-15T08:00-04:00', + end: '2022-11-15T08:00-05:00', season: 'normal', npcImageSuffix: '', }, From 5e05190f22fca237dd6d07dfcd70a5b9cb28f8e3 Mon Sep 17 00:00:00 2001 From: SabreCat Date: Tue, 15 Nov 2022 16:05:06 -0600 Subject: [PATCH 6/9] fix(event): start at 8AM not 8PM --- website/common/script/content/constants/events.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/common/script/content/constants/events.js b/website/common/script/content/constants/events.js index 8e54486499..54fb486e8d 100644 --- a/website/common/script/content/constants/events.js +++ b/website/common/script/content/constants/events.js @@ -16,8 +16,8 @@ export const EVENTS = { npcImageSuffix: '', }, bundle202211: { - start: '2022-11-15T20:00-05:00', - end: '2022-11-30T08:00-05:00', + start: '2022-11-15T08:00-05:00', + end: '2022-11-30T20:00-05:00', season: 'normal', npcImageSuffix: '', }, From 1a5cba57b7e45cc14fa2a4bc752fa2826e21bc58 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 15 Nov 2022 19:19:37 -0600 Subject: [PATCH 7/9] Fix double subscriptions, second attempt (#14345) * fix(subscriptions): reject subs that come in too fast * fix(lint): remove unused import * fix(groups): individual subs may come rapidly * fix(subscriptions): bad paren, handle rapid testing * fix(test): reset dateUpdated between subs * fix(test): one more block for dateUpdated Co-authored-by: SabreCat --- test/api/unit/libs/payments/apple.test.js | 3 ++ test/api/unit/libs/payments/payments.test.js | 13 ++++++ website/server/libs/payments/subscriptions.js | 40 ++++--------------- 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/test/api/unit/libs/payments/apple.test.js b/test/api/unit/libs/payments/apple.test.js index 1aefe2cb51..a086677292 100644 --- a/test/api/unit/libs/payments/apple.test.js +++ b/test/api/unit/libs/payments/apple.test.js @@ -326,9 +326,12 @@ describe('Apple Payments', () => { it('errors when a user is already subscribed', async () => { payments.createSubscription.restore(); user = new User(); + user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); await user.save(); await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing); + user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); + await user.save(); await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing)) .to.eventually.be.rejected.and.to.eql({ diff --git a/test/api/unit/libs/payments/payments.test.js b/test/api/unit/libs/payments/payments.test.js index 48d6325546..7e306f94fd 100644 --- a/test/api/unit/libs/payments/payments.test.js +++ b/test/api/unit/libs/payments/payments.test.js @@ -350,6 +350,10 @@ describe('payments/index', () => { }); context('Purchasing a subscription for self', () => { + beforeEach(() => { + data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); + }); + it('creates a subscription', async () => { expect(user.purchased.plan.planId).to.not.exist; @@ -376,6 +380,7 @@ describe('payments/index', () => { user.purchased.plan = plan; user.purchased.plan.dateTerminated = moment(new Date()).add(2, 'months'); expect(user.purchased.plan.extraMonths).to.eql(0); + data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); await api.createSubscription(data); @@ -386,6 +391,7 @@ describe('payments/index', () => { user.purchased.plan = plan; user.purchased.plan.dateTerminated = moment(new Date()).subtract(2, 'months'); expect(user.purchased.plan.extraMonths).to.eql(0); + data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); await api.createSubscription(data); @@ -395,6 +401,7 @@ describe('payments/index', () => { it('does not reset Gold-to-Gems cap on additional subscription', async () => { user.purchased.plan = plan; user.purchased.plan.gemsBought = 10; + data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); await api.createSubscription(data); @@ -448,6 +455,10 @@ describe('payments/index', () => { }); context('Block subscription perks', () => { + beforeEach(() => { + data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); + }); + it('adds block months to plan.consecutive.offset', async () => { await api.createSubscription(data); @@ -486,6 +497,7 @@ describe('payments/index', () => { data.sub.key = 'basic_12mo'; await api.createSubscription(data); + data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); await api.createSubscription(data); expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25); @@ -524,6 +536,7 @@ describe('payments/index', () => { now: mayMysteryItemTimeframe, toFake: ['Date'], }); + data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); }); afterEach(() => { diff --git a/website/server/libs/payments/subscriptions.js b/website/server/libs/payments/subscriptions.js index 80401f21de..1387d4b5c8 100644 --- a/website/server/libs/payments/subscriptions.js +++ b/website/server/libs/payments/subscriptions.js @@ -11,10 +11,10 @@ import { // eslint-disable-line import/no-cycle model as Group, basicFields as basicGroupFields, } from '../../models/group'; -import { model as User } from '../../models/user'; // eslint-disable-line import/no-cycle import { NotAuthorized, NotFound, + TooManyRequests, } from '../errors'; import shared from '../../../common'; import { sendNotification as sendPushNotification } from '../pushNotifications'; // eslint-disable-line import/no-cycle @@ -80,19 +80,9 @@ async function createSubscription (data) { let emailType = 'subscription-begins'; let recipientIsSubscribed = recipient.isSubscribed(); - if (data.user && !data.gift && !data.groupId) { - const unlockedUser = await User.findOneAndUpdate( - { - _id: data.user._id, - $or: [ - { _subSignature: 'NOT_RUNNING' }, - { _subSignature: { $exists: false } }, - ], - }, - { $set: { _subSignature: 'SUB_IN_PROGRESS' } }, - ); - if (!unlockedUser) { - throw new NotFound('User not found or subscription already processing.'); + if (data.user && !data.gift && !data.groupId && data.customerId !== 'group-plan') { + if (moment().diff(data.user.purchased.plan.dateUpdated, 'minutes') < 3) { + throw new TooManyRequests('Subscription already processed, likely duplicate request'); } } @@ -299,6 +289,10 @@ async function createSubscription (data) { } } + if (group) await group.save(); + if (data.user && data.user.isModified()) await data.user.save(); + if (data.gift) await data.gift.member.save(); + slack.sendSubscriptionNotification({ buyer: { id: data.user._id, @@ -315,24 +309,6 @@ async function createSubscription (data) { groupId, autoRenews, }); - - if (group) { - await group.save(); - } - if (data.user) { - if (data.user.isModified()) { - await data.user.save(); - } - if (!data.gift && !data.groupId) { - await User.findOneAndUpdate( - { _id: data.user._id }, - { $set: { _subSignature: 'NOT_RUNNING' } }, - ); - } - } - if (data.gift) { - await data.gift.member.save(); - } } // Cancels a subscription or group plan, setting termination to happen later From 259131ee3fb64148a185f9f7634b32b709879865 Mon Sep 17 00:00:00 2001 From: SabreCat Date: Tue, 15 Nov 2022 19:28:36 -0600 Subject: [PATCH 8/9] feat(transactions): UI updates by @negue --- website/client/src/assets/svg/gem-red.svg | 10 + .../client/src/assets/svg/hourglass-red.svg | 10 + .../admin-panel/user-support/index.vue | 1 + .../admin-panel/user-support/transactions.vue | 12 + .../components/ui/purchaseHistoryTable.vue | 368 ++++++++++++++---- website/common/locales/en/settings.json | 31 +- website/server/controllers/api-v3/hall.js | 2 +- website/server/models/transaction.js | 2 +- 8 files changed, 350 insertions(+), 86 deletions(-) create mode 100644 website/client/src/assets/svg/gem-red.svg create mode 100644 website/client/src/assets/svg/hourglass-red.svg diff --git a/website/client/src/assets/svg/gem-red.svg b/website/client/src/assets/svg/gem-red.svg new file mode 100644 index 0000000000..aa1504893d --- /dev/null +++ b/website/client/src/assets/svg/gem-red.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/website/client/src/assets/svg/hourglass-red.svg b/website/client/src/assets/svg/hourglass-red.svg new file mode 100644 index 0000000000..7a7a9e0d32 --- /dev/null +++ b/website/client/src/assets/svg/hourglass-red.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/website/client/src/components/admin-panel/user-support/index.vue b/website/client/src/components/admin-panel/user-support/index.vue index 8d156077f2..91be09e8b6 100644 --- a/website/client/src/components/admin-panel/user-support/index.vue +++ b/website/client/src/components/admin-panel/user-support/index.vue @@ -49,6 +49,7 @@ -
-
-

{{ $t('gemTransactions') }}

- {{ $t('noGemTransactions') }} - - +
+
+
- - - - -
- {{ entry.createdAt | timeAgo }} - - - {{ entry.amount * 4 }} - - {{ transactionTypeText(entry.transactionType) }} - - -
-
-
-

{{ $t('hourglassTransactions') }}

- {{ $t('noHourglassTransactions') }} - - + - - - - -
- {{ entry.createdAt | timeAgo }} - - - {{ entry.amount }} - - {{ transactionTypeText(entry.transactionType) }} - - -
+ {{ $t('mysticHourglass', { amount: ''}) }} + +
+ +
+
+ + {{ $t('noGemTransactions') }} + + + + + + + + + + + + + + +
+ {{ $t('timestamp')}} + + {{ $t('amount')}} + + {{ $t('action')}} + + {{ $t('note')}} +
+ {{ entry.createdAt | timeAgo }} + +
+ + {{ entry.amount * 4 }} +
+ + +
+ {{ $t('remainingBalance') }}: + + {{ entry.currentAmount * 4 }} +
+
+
+ + + + + @{{ entry.referenceText }} + + + + + + + + + + + ({{entry.reference}}) + +
+
+
+ + {{ $t('noHourglassTransactions') }} + + + + + + + + + + + + + + +
+ {{ $t('timestamp')}} + + {{ $t('amount')}} + + {{ $t('action')}} + + {{ $t('note')}} +
+ {{ entry.createdAt | timeAgo }} + +
+ + {{ entry.amount }} +
+ + +
+ {{ $t('remainingBalance') }}: + + {{ entry.currentAmount }} +
+
+
+ + + +
+
+
+ - @@ -107,9 +310,15 @@