diff --git a/config.json.example b/config.json.example index 3875097d6c..9872fad88f 100644 --- a/config.json.example +++ b/config.json.example @@ -86,5 +86,6 @@ "RATE_LIMITER_ENABLED": "false", "REDIS_HOST": "aaabbbcccdddeeefff", "REDIS_PORT": "1234", - "REDIS_PASSWORD": "12345678" + "REDIS_PASSWORD": "12345678", + "TRUSTED_DOMAINS": "https://localhost,https://habitica.com" } diff --git a/habitica-images b/habitica-images index 7051701604..311cb01059 160000 --- a/habitica-images +++ b/habitica-images @@ -1 +1 @@ -Subproject commit 7051701604ff6ea246bf96995f4869ca95696d88 +Subproject commit 311cb010591a9de368a1ab5db7506d2d3efbb10b diff --git a/migrations/users/pi-day.js b/migrations/users/pi-day.js index f4af8ff143..dd3f7d4f3f 100644 --- a/migrations/users/pi-day.js +++ b/migrations/users/pi-day.js @@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid'; import { model as User } from '../../website/server/models/user'; -const MIGRATION_NAME = '20220314_pi_day'; +const MIGRATION_NAME = '20230314_pi_day'; const progressCount = 1000; let count = 0; @@ -54,7 +54,7 @@ async function updateUser (user) { export default async function processUsers () { const query = { migration: { $ne: MIGRATION_NAME }, - 'auth.timestamps.loggedin': { $gt: new Date('2022-02-15') }, + 'auth.timestamps.loggedin': { $gt: new Date('2023-02-15') }, }; const fields = { diff --git a/package-lock.json b/package-lock.json index e2b06a9914..e43131bc51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "habitica", - "version": "4.259.0", + "version": "4.269.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "requires": { - "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" } }, @@ -27,20 +27,20 @@ "integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==" }, "@babel/core": { - "version": "7.20.12", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", - "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", + "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helpers": "^7.20.7", - "@babel/parser": "^7.20.7", + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.4", "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.12", - "@babel/types": "^7.20.7", + "@babel/traverse": "^7.21.4", + "@babel/types": "^7.21.4", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -49,44 +49,54 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", "requires": { "@babel/highlight": "^7.18.6" } }, "@babel/compat-data": { - "version": "7.20.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", - "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==" + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", + "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==" }, "@babel/generator": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", - "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", + "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", "requires": { - "@babel/types": "^7.20.7", + "@babel/types": "^7.21.4", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" } }, "@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", + "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", + "@babel/compat-data": "^7.21.4", + "@babel/helper-validator-option": "^7.21.0", "browserslist": "^4.21.3", "lru-cache": "^5.1.1", "semver": "^6.3.0" } }, + "@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "requires": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + } + }, "@babel/helper-module-transforms": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", - "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", + "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", "requires": { "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-module-imports": "^7.18.6", @@ -94,8 +104,8 @@ "@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-validator-identifier": "^7.19.1", "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.10", - "@babel/types": "^7.20.7" + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2" } }, "@babel/helper-validator-identifier": { @@ -103,6 +113,11 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" }, + "@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==" + }, "@babel/highlight": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", @@ -114,9 +129,9 @@ } }, "@babel/parser": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", - "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==" + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", + "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==" }, "@babel/template": { "version": "7.20.7", @@ -129,57 +144,50 @@ } }, "@babel/traverse": { - "version": "7.20.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz", - "integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", + "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", + "@babel/helper-function-name": "^7.21.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", + "@babel/parser": "^7.21.4", + "@babel/types": "^7.21.4", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", - "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", + "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", "requires": { "@babel/helper-string-parser": "^7.19.4", "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" } }, - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" }, - "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - } + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, - "caniuse-lite": { - "version": "1.0.30001442", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz", - "integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==" + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } }, "chalk": { "version": "2.4.2", @@ -191,11 +199,6 @@ "supports-color": "^5.3.0" } }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" - }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -209,15 +212,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, - "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -666,33 +660,43 @@ } }, "@babel/helpers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", - "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", "requires": { "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" }, "dependencies": { "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", "requires": { "@babel/highlight": "^7.18.6" } }, "@babel/generator": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", - "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", + "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", "requires": { - "@babel/types": "^7.20.7", + "@babel/types": "^7.21.4", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" } }, + "@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "requires": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + } + }, "@babel/helper-validator-identifier": { "version": "7.19.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", @@ -709,9 +713,9 @@ } }, "@babel/parser": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", - "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==" + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", + "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==" }, "@babel/template": { "version": "7.20.7", @@ -724,40 +728,49 @@ } }, "@babel/traverse": { - "version": "7.20.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz", - "integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", + "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", + "@babel/helper-function-name": "^7.21.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", + "@babel/parser": "^7.21.4", + "@babel/types": "^7.21.4", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", - "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", + "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", "requires": { "@babel/helper-string-parser": "^7.19.4", "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" } }, - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, "chalk": { @@ -1487,9 +1500,9 @@ } }, "@babel/register": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.18.9.tgz", - "integrity": "sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.21.0.tgz", + "integrity": "sha512-9nKsPmYDi5DidAqJaQooxIhsLJiNMkGr8ypQ8Uic7cIox7UCDsM7HuUGxdGT7mSDTYbqzIdsOWzfBton/YJrMw==", "requires": { "clone-deep": "^4.0.1", "find-cache-dir": "^2.0.0", @@ -1667,15 +1680,135 @@ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==" }, "@esbuild/android-arm": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.12.tgz", - "integrity": "sha512-IC7TqIqiyE0MmvAhWkl/8AEzpOtbhRNDo7aph47We1NbE5w2bt/Q+giAhe0YYeVpYnIhGMcuZY92qDK6dQauvA==", + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", + "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", + "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", + "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", + "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", + "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", + "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", + "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", + "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", + "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", + "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", "optional": true }, "@esbuild/linux-loong64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.12.tgz", - "integrity": "sha512-tZEowDjvU7O7I04GYvWQOS4yyP9E/7YlsB0jjw1Ycukgr2ycEzKyIk5tms5WnLBymaewc6VmRKnn5IJWgK4eFw==", + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", + "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", + "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", + "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", + "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", + "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz", + "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==", + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", + "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", + "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", + "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", + "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", + "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", + "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", "optional": true }, "@google-cloud/common": { @@ -1743,12 +1876,13 @@ } }, "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" } }, "@jridgewell/resolve-uri": { @@ -2033,9 +2167,9 @@ "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==" }, "@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -2048,17 +2182,39 @@ "dev": true, "requires": { "@sinonjs/commons": "^2.0.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } } }, "@sinonjs/samsam": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", - "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", "dev": true, "requires": { "@sinonjs/commons": "^2.0.0", "lodash.get": "^4.4.2", "type-detect": "^4.0.8" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } } }, "@sinonjs/text-encoding": { @@ -2147,9 +2303,9 @@ } }, "@types/eslint": { - "version": "8.4.7", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.7.tgz", - "integrity": "sha512-ehM7cCt2RSFs42mb+lcmhFT9ouIlV92PuaeRGn8N8c98oMjG4Z5pJHA9b1QiCcuqnbPSHcyfiD3mlhqMaHsQIw==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.0.tgz", + "integrity": "sha512-35EhHNOXgxnUgh4XCJsGhE7zdlDhYDN/aMG6UbkByCFFNgQ7b3U+uVoqBpicFydR8JEfgdjCF7SJ7MiJfzuiTA==", "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -2636,9 +2792,9 @@ } }, "apidoc": { - "version": "0.53.1", - "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.53.1.tgz", - "integrity": "sha512-ijiLtIVEzTMdF29B/QzkvR4weMatgcElMsYKP1asszrImWYwzlZ9x0ZMLTXZrCe7GVMtkGSwQdugdLTZMZ+lww==", + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.54.0.tgz", + "integrity": "sha512-VCOdwkAaFK7bDLbiAiFKqX8SVlAnk7sQCXDARwshBQpVRqRGDNY993kAMPkWmSD1RCVBDMFIOgOfOR9mcISNZQ==", "requires": { "bootstrap": "3.4.1", "commander": "^8.3.0", @@ -2663,9 +2819,9 @@ }, "dependencies": { "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2829,13 +2985,13 @@ } }, "apple-auth": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/apple-auth/-/apple-auth-1.0.7.tgz", - "integrity": "sha512-vfJqy4KtT5KHflxBSemc0mkWuy2GU2wHWaZ6xVWUjPmiXkJcLj5jB3IeCbDLF4wN+ZStjOQXZWfGwccBjclVlA==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/apple-auth/-/apple-auth-1.0.9.tgz", + "integrity": "sha512-nm3h/sXpwA6kyBWa1l8Sl3c8nusIQMnUklBL8eTUDb++LpF2k2GZ66OaDj5lba7Arw9JpHfuzewBF92mh9lNPQ==", "requires": { "axios": "^0.21.1", "express": "^4.17.1", - "jsonwebtoken": "^8.5.1" + "jsonwebtoken": "^9.0.0" }, "dependencies": { "axios": { @@ -2845,6 +3001,44 @@ "requires": { "follow-redirects": "^1.14.0" } + }, + "jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "requires": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } } } }, @@ -3152,9 +3346,9 @@ "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" }, "axios": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz", - "integrity": "sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz", + "integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==", "dev": true, "requires": { "follow-redirects": "^1.15.0", @@ -3922,12 +4116,12 @@ "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" }, "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -3935,11 +4129,16 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, "dependencies": { + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3976,6 +4175,17 @@ "requires": { "side-channel": "^1.0.4" } + }, + "raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } } } }, @@ -4075,20 +4285,25 @@ "dev": true }, "browserslist": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.2.tgz", - "integrity": "sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA==", + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", "requires": { - "caniuse-lite": "^1.0.30001366", - "electron-to-chromium": "^1.4.188", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.4" + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" }, "dependencies": { "electron-to-chromium": { - "version": "1.4.192", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.192.tgz", - "integrity": "sha512-8nCXyIQY9An88NXAp+PuPy5h3/w5ZY7Iu2lag65Q0XREprcat5F8gKhoHsBUnQcFuCRnmevpR8yEBYRU3d2HDw==" + "version": "1.4.295", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.295.tgz", + "integrity": "sha512-lEO94zqf1bDA3aepxwnWoHUjA8sZ+2owgcSZjYQy0+uOSEclJX0VieZC+r+wLpSxUHRd6gG32znTWmr+5iGzFw==" + }, + "node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" } } }, @@ -4246,9 +4461,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001367", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001367.tgz", - "integrity": "sha512-XDgbeOHfifWV3GEES2B8rtsrADx4Jf+juKX2SICJcaUhjYBO3bR96kvEIHa15VU6ohtOhBZuPGGYGbXMRn0NCw==" + "version": "1.0.30001451", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001451.tgz", + "integrity": "sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w==" }, "caseless": { "version": "0.12.0", @@ -4879,9 +5094,9 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" }, "cookies": { "version": "0.8.0", @@ -5759,9 +5974,9 @@ } }, "enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -5871,167 +6086,47 @@ } }, "esbuild": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.12.tgz", - "integrity": "sha512-PcT+/wyDqJQsRVhaE9uX/Oq4XLrFh0ce/bs2TJh4CSaw9xuvI+xFrH2nAYOADbhQjUgAhNWC5LKoUsakm4dxng==", + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", + "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", "requires": { - "@esbuild/android-arm": "0.15.12", - "@esbuild/linux-loong64": "0.15.12", - "esbuild-android-64": "0.15.12", - "esbuild-android-arm64": "0.15.12", - "esbuild-darwin-64": "0.15.12", - "esbuild-darwin-arm64": "0.15.12", - "esbuild-freebsd-64": "0.15.12", - "esbuild-freebsd-arm64": "0.15.12", - "esbuild-linux-32": "0.15.12", - "esbuild-linux-64": "0.15.12", - "esbuild-linux-arm": "0.15.12", - "esbuild-linux-arm64": "0.15.12", - "esbuild-linux-mips64le": "0.15.12", - "esbuild-linux-ppc64le": "0.15.12", - "esbuild-linux-riscv64": "0.15.12", - "esbuild-linux-s390x": "0.15.12", - "esbuild-netbsd-64": "0.15.12", - "esbuild-openbsd-64": "0.15.12", - "esbuild-sunos-64": "0.15.12", - "esbuild-windows-32": "0.15.12", - "esbuild-windows-64": "0.15.12", - "esbuild-windows-arm64": "0.15.12" + "@esbuild/android-arm": "0.16.17", + "@esbuild/android-arm64": "0.16.17", + "@esbuild/android-x64": "0.16.17", + "@esbuild/darwin-arm64": "0.16.17", + "@esbuild/darwin-x64": "0.16.17", + "@esbuild/freebsd-arm64": "0.16.17", + "@esbuild/freebsd-x64": "0.16.17", + "@esbuild/linux-arm": "0.16.17", + "@esbuild/linux-arm64": "0.16.17", + "@esbuild/linux-ia32": "0.16.17", + "@esbuild/linux-loong64": "0.16.17", + "@esbuild/linux-mips64el": "0.16.17", + "@esbuild/linux-ppc64": "0.16.17", + "@esbuild/linux-riscv64": "0.16.17", + "@esbuild/linux-s390x": "0.16.17", + "@esbuild/linux-x64": "0.16.17", + "@esbuild/netbsd-x64": "0.16.17", + "@esbuild/openbsd-x64": "0.16.17", + "@esbuild/sunos-x64": "0.16.17", + "@esbuild/win32-arm64": "0.16.17", + "@esbuild/win32-ia32": "0.16.17", + "@esbuild/win32-x64": "0.16.17" } }, - "esbuild-android-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.12.tgz", - "integrity": "sha512-MJKXwvPY9g0rGps0+U65HlTsM1wUs9lbjt5CU19RESqycGFDRijMDQsh68MtbzkqWSRdEtiKS1mtPzKneaAI0Q==", - "optional": true - }, - "esbuild-android-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.12.tgz", - "integrity": "sha512-Hc9SEcZbIMhhLcvhr1DH+lrrec9SFTiRzfJ7EGSBZiiw994gfkVV6vG0sLWqQQ6DD7V4+OggB+Hn0IRUdDUqvA==", - "optional": true - }, - "esbuild-darwin-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.12.tgz", - "integrity": "sha512-qkmqrTVYPFiePt5qFjP8w/S+GIUMbt6k8qmiPraECUWfPptaPJUGkCKrWEfYFRWB7bY23FV95rhvPyh/KARP8Q==", - "optional": true - }, - "esbuild-darwin-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.12.tgz", - "integrity": "sha512-z4zPX02tQ41kcXMyN3c/GfZpIjKoI/BzHrdKUwhC/Ki5BAhWv59A9M8H+iqaRbwpzYrYidTybBwiZAIWCLJAkw==", - "optional": true - }, - "esbuild-freebsd-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.12.tgz", - "integrity": "sha512-XFL7gKMCKXLDiAiBjhLG0XECliXaRLTZh6hsyzqUqPUf/PY4C6EJDTKIeqqPKXaVJ8+fzNek88285krSz1QECw==", - "optional": true - }, - "esbuild-freebsd-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.12.tgz", - "integrity": "sha512-jwEIu5UCUk6TjiG1X+KQnCGISI+ILnXzIzt9yDVrhjug2fkYzlLbl0K43q96Q3KB66v6N1UFF0r5Ks4Xo7i72g==", - "optional": true - }, - "esbuild-linux-32": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.12.tgz", - "integrity": "sha512-uSQuSEyF1kVzGzuIr4XM+v7TPKxHjBnLcwv2yPyCz8riV8VUCnO/C4BF3w5dHiVpCd5Z1cebBtZJNlC4anWpwA==", - "optional": true - }, - "esbuild-linux-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.12.tgz", - "integrity": "sha512-QcgCKb7zfJxqT9o5z9ZUeGH1k8N6iX1Y7VNsEi5F9+HzN1OIx7ESxtQXDN9jbeUSPiRH1n9cw6gFT3H4qbdvcA==", - "optional": true - }, - "esbuild-linux-arm": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.12.tgz", - "integrity": "sha512-Wf7T0aNylGcLu7hBnzMvsTfEXdEdJY/hY3u36Vla21aY66xR0MS5I1Hw8nVquXjTN0A6fk/vnr32tkC/C2lb0A==", - "optional": true - }, - "esbuild-linux-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.12.tgz", - "integrity": "sha512-HtNq5xm8fUpZKwWKS2/YGwSfTF+339L4aIA8yphNKYJckd5hVdhfdl6GM2P3HwLSCORS++++7++//ApEwXEuAQ==", - "optional": true - }, - "esbuild-linux-mips64le": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.12.tgz", - "integrity": "sha512-Qol3+AvivngUZkTVFgLpb0H6DT+N5/zM3V1YgTkryPYFeUvuT5JFNDR3ZiS6LxhyF8EE+fiNtzwlPqMDqVcc6A==", - "optional": true - }, - "esbuild-linux-ppc64le": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.12.tgz", - "integrity": "sha512-4D8qUCo+CFKaR0cGXtGyVsOI7w7k93Qxb3KFXWr75An0DHamYzq8lt7TNZKoOq/Gh8c40/aKaxvcZnTgQ0TJNg==", - "optional": true - }, - "esbuild-linux-riscv64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.12.tgz", - "integrity": "sha512-G9w6NcuuCI6TUUxe6ka0enjZHDnSVK8bO+1qDhMOCtl7Tr78CcZilJj8SGLN00zO5iIlwNRZKHjdMpfFgNn1VA==", - "optional": true - }, - "esbuild-linux-s390x": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.12.tgz", - "integrity": "sha512-Lt6BDnuXbXeqSlVuuUM5z18GkJAZf3ERskGZbAWjrQoi9xbEIsj/hEzVnSAFLtkfLuy2DE4RwTcX02tZFunXww==", - "optional": true - }, "esbuild-loader": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/esbuild-loader/-/esbuild-loader-2.20.0.tgz", - "integrity": "sha512-dr+j8O4w5RvqZ7I4PPB4EIyVTd679EBQnMm+JBB7av+vu05Zpje2IpK5N3ld1VWa+WxrInIbNFAg093+E1aRsA==", + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/esbuild-loader/-/esbuild-loader-2.21.0.tgz", + "integrity": "sha512-k7ijTkCT43YBSZ6+fBCW1Gin7s46RrJ0VQaM8qA7lq7W+OLsGgtLyFV8470FzYi/4TeDexniTBTPTwZUnXXR5g==", "requires": { - "esbuild": "^0.15.6", + "esbuild": "^0.16.17", "joycon": "^3.0.1", "json5": "^2.2.0", "loader-utils": "^2.0.0", "tapable": "^2.2.0", - "webpack-sources": "^2.2.0" + "webpack-sources": "^1.4.3" } }, - "esbuild-netbsd-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.12.tgz", - "integrity": "sha512-jlUxCiHO1dsqoURZDQts+HK100o0hXfi4t54MNRMCAqKGAV33JCVvMplLAa2FwviSojT/5ZG5HUfG3gstwAG8w==", - "optional": true - }, - "esbuild-openbsd-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.12.tgz", - "integrity": "sha512-1o1uAfRTMIWNOmpf8v7iudND0L6zRBYSH45sofCZywrcf7NcZA+c7aFsS1YryU+yN7aRppTqdUK1PgbZVaB1Dw==", - "optional": true - }, - "esbuild-sunos-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.12.tgz", - "integrity": "sha512-nkl251DpoWoBO9Eq9aFdoIt2yYmp4I3kvQjba3jFKlMXuqQ9A4q+JaqdkCouG3DHgAGnzshzaGu6xofGcXyPXg==", - "optional": true - }, - "esbuild-windows-32": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.12.tgz", - "integrity": "sha512-WlGeBZHgPC00O08luIp5B2SP4cNCp/PcS+3Pcg31kdcJPopHxLkdCXtadLU9J82LCfw4TVls21A6lilQ9mzHrw==", - "optional": true - }, - "esbuild-windows-64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.12.tgz", - "integrity": "sha512-VActO3WnWZSN//xjSfbiGOSyC+wkZtI8I4KlgrTo5oHJM6z3MZZBCuFaZHd8hzf/W9KPhF0lY8OqlmWC9HO5AA==", - "optional": true - }, - "esbuild-windows-arm64": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.12.tgz", - "integrity": "sha512-Of3MIacva1OK/m4zCNIvBfz8VVROBmQT+gRX6pFTLPngFYcj6TFH/12VveAqq1k9VB2l28EoVMNMUCcmsfwyuA==", - "optional": true - }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -7729,9 +7824,9 @@ "dev": true }, "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7749,9 +7844,9 @@ } }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "requires": { "brace-expansion": "^2.0.1" } @@ -9704,9 +9799,9 @@ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==" }, "jquery": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.1.tgz", - "integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz", + "integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==" }, "js-tokens": { "version": "4.0.0", @@ -9784,9 +9879,9 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", - "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==" + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, "jsonfile": { "version": "6.1.0", @@ -11396,36 +11491,25 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "nise": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.3.tgz", - "integrity": "sha512-U597iWTTBBYIV72986jyU382/MMZ70ApWcRmkoF1AZ75bpqOtI3Gugv/6+0jLgoDOabmcSwYBkSSAWIp1eA5cg==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", + "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", "dev": true, "requires": { "@sinonjs/commons": "^2.0.0", - "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/fake-timers": "^10.0.2", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", "path-to-regexp": "^1.7.0" }, "dependencies": { - "@sinonjs/fake-timers": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", - "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0" - }, - "dependencies": { - "@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - } + "type-detect": "4.0.8" } }, "isarray": { @@ -13451,9 +13535,9 @@ } }, "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "requires": { "randombytes": "^2.1.0" } @@ -13612,9 +13696,9 @@ } }, "simple-update-notifier": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", - "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", "requires": { "semver": "~7.0.0" }, @@ -13627,16 +13711,16 @@ } }, "sinon": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.1.tgz", - "integrity": "sha512-PZXKc08f/wcA/BMRGBze2Wmw50CWPiAH3E21EOi4B49vJ616vW4DQh4fQrqsYox2aNR/N3kCqLuB0PwwOucQrg==", + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.4.tgz", + "integrity": "sha512-uzmfN6zx3GQaria1kwgWGeKiXSSbShBbue6Dcj0SI8fiCNFbiUDqKl57WFlY5lyhxZVUKmXvzgG2pilRQCBwWg==", "dev": true, "requires": { - "@sinonjs/commons": "^2.0.0", - "@sinonjs/fake-timers": "10.0.2", - "@sinonjs/samsam": "^7.0.1", - "diff": "^5.0.0", - "nise": "^5.1.2", + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.4", "supports-color": "^7.2.0" }, "dependencies": { @@ -14286,9 +14370,9 @@ } }, "stripe": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-11.6.0.tgz", - "integrity": "sha512-ht8S1l8CJJE3jtv2NM1mEQzZBkITYvb9uDpSeXYeNz9iJkFFgDU169htwOW00OdIESvFaIsGgWQatAE5dfOERQ==", + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-11.10.0.tgz", + "integrity": "sha512-uzQuj/Vangpp8fgkaEr6oRNfPToo5xamOhc7wxFGF/RUm7VKm/IGzoZ0cCtnGHCs5/O1AjMQsTZApgUARRVHOw==", "requires": { "@types/node": ">=8.1.0", "qs": "^6.11.0" @@ -14600,9 +14684,9 @@ "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==" }, "terser": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz", - "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==", + "version": "5.16.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.3.tgz", + "integrity": "sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q==", "requires": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", @@ -14611,9 +14695,9 @@ }, "dependencies": { "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==" + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==" }, "commander": { "version": "2.20.3", @@ -14992,9 +15076,9 @@ }, "dependencies": { "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "requires": { "minimist": "^1.2.0" } @@ -15310,9 +15394,9 @@ "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" }, "update-browserslist-db": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz", - "integrity": "sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", "requires": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -15514,9 +15598,9 @@ } }, "validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", + "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==" }, "value-or-function": { "version": "3.0.0", @@ -15782,9 +15866,9 @@ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "version": "5.75.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", + "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", "requires": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", @@ -15813,9 +15897,9 @@ }, "dependencies": { "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==" + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==" }, "eslint-scope": { "version": "5.1.1", @@ -15902,12 +15986,12 @@ } }, "webpack-sources": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", - "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", "requires": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" }, "dependencies": { "source-map": { @@ -16149,9 +16233,9 @@ } }, "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" diff --git a/package.json b/package.json index f5c9da55c7..fd55d4a746 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,22 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "4.259.0", + "version": "4.269.0", "main": "./website/server/index.js", "dependencies": { - "@babel/core": "^7.20.12", + "@babel/core": "^7.21.4", "@babel/preset-env": "^7.20.2", - "@babel/register": "^7.18.9", + "@babel/register": "^7.21.0", "@google-cloud/trace-agent": "^7.1.2", "@parse/node-apn": "^5.1.3", "@slack/webhook": "^6.1.0", "accepts": "^1.3.8", "amazon-payments": "^0.2.9", "amplitude": "^6.0.0", - "apidoc": "^0.53.1", - "apple-auth": "^1.0.7", + "apidoc": "^0.54.0", + "apple-auth": "^1.0.9", "bcrypt": "^5.1.0", - "body-parser": "^1.20.1", + "body-parser": "^1.20.2", "bootstrap": "^4.6.0", "compression": "^1.7.4", "cookie-session": "^2.0.0", @@ -30,7 +30,7 @@ "express": "^4.18.2", "express-basic-auth": "^1.2.1", "express-validator": "^5.2.0", - "glob": "^8.0.3", + "glob": "^8.1.0", "got": "^11.8.3", "gulp": "^4.0.0", "gulp-babel": "^8.0.0", @@ -67,16 +67,16 @@ "remove-markdown": "^0.5.0", "rimraf": "^3.0.2", "short-uuid": "^4.2.2", - "stripe": "^11.6.0", + "stripe": "^11.10.0", "superagent": "^8.0.6", "universal-analytics": "^0.5.3", "useragent": "^2.1.9", "uuid": "^9.0.0", - "validator": "^13.7.0", + "validator": "^13.9.0", "vinyl-buffer": "^1.0.1", "winston": "^3.8.2", "winston-loggly-bulk": "^3.2.1", - "xml2js": "^0.4.23" + "xml2js": "^0.5.0" }, "private": true, "engines": { @@ -110,7 +110,7 @@ "apidoc": "gulp apidoc" }, "devDependencies": { - "axios": "^1.2.2", + "axios": "^1.3.6", "chai": "^4.3.7", "chai-as-promised": "^7.1.1", "chai-moment": "^0.1.0", @@ -122,7 +122,7 @@ "monk": "^7.3.4", "require-again": "^2.0.0", "run-rs": "^0.7.7", - "sinon": "^15.0.1", + "sinon": "^15.0.4", "sinon-chai": "^3.7.0", "sinon-stub-promise": "^4.0.0" }, diff --git a/test/api/unit/libs/cron.test.js b/test/api/unit/libs/cron.test.js index eb15119ee1..6757228033 100644 --- a/test/api/unit/libs/cron.test.js +++ b/test/api/unit/libs/cron.test.js @@ -231,13 +231,16 @@ describe('cron', async () => { }, }); // user1 has a 1-month recurring subscription starting today - user1.purchased.plan.customerId = 'subscribedId'; - user1.purchased.plan.dateUpdated = moment().toDate(); - user1.purchased.plan.planId = 'basic'; - user1.purchased.plan.consecutive.count = 0; - user1.purchased.plan.consecutive.offset = 0; - user1.purchased.plan.consecutive.trinkets = 0; - user1.purchased.plan.consecutive.gemCapExtra = 0; + beforeEach(async () => { + user1.purchased.plan.customerId = 'subscribedId'; + user1.purchased.plan.dateUpdated = moment().toDate(); + user1.purchased.plan.planId = 'basic'; + user1.purchased.plan.consecutive.count = 0; + user1.purchased.plan.perkMonthCount = 0; + user1.purchased.plan.consecutive.offset = 0; + user1.purchased.plan.consecutive.trinkets = 0; + user1.purchased.plan.consecutive.gemCapExtra = 0; + }); it('does not increment consecutive benefits after the first month', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') @@ -271,6 +274,24 @@ describe('cron', async () => { expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0); }); + it('increments consecutive benefits after the second month if they also received a 1 month gift subscription', async () => { + user1.purchased.plan.perkMonthCount = 1; + clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') + .add(2, 'days') + .toDate()); + // Add 1 month to simulate what happens a month after the subscription was created. + // Add 2 days so that we're sure we're not affected by any start-of-month effects + // e.g., from time zone oddness. + await cron({ + user: user1, tasksByType, daysMissed, analytics, + }); + expect(user1.purchased.plan.perkMonthCount).to.equal(0); + expect(user1.purchased.plan.consecutive.count).to.equal(2); + expect(user1.purchased.plan.consecutive.offset).to.equal(0); + expect(user1.purchased.plan.consecutive.trinkets).to.equal(1); + expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5); + }); + it('increments consecutive benefits after the third month', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months') .add(2, 'days') @@ -315,6 +336,30 @@ describe('cron', async () => { expect(user1.purchased.plan.consecutive.trinkets).to.equal(3); expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15); }); + + it('initializes plan.perkMonthCount if necessary', async () => { + user.purchased.plan.perkMonthCount = undefined; + clock = sinon.useFakeTimers(moment(user.purchased.plan.dateUpdated) + .utcOffset(0) + .startOf('month') + .add(1, 'months') + .add(2, 'days') + .toDate()); + await cron({ + user, tasksByType, daysMissed, analytics, + }); + expect(user.purchased.plan.perkMonthCount).to.equal(1); + user.purchased.plan.perkMonthCount = undefined; + user.purchased.plan.consecutive.count = 8; + clock.restore(); + clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') + .add(2, 'days') + .toDate()); + await cron({ + user, tasksByType, daysMissed, analytics, + }); + expect(user.purchased.plan.perkMonthCount).to.equal(2); + }); }); describe('for a 3-month recurring subscription', async () => { @@ -330,13 +375,16 @@ describe('cron', async () => { }, }); // user3 has a 3-month recurring subscription starting today - user3.purchased.plan.customerId = 'subscribedId'; - user3.purchased.plan.dateUpdated = moment().toDate(); - user3.purchased.plan.planId = 'basic_3mo'; - user3.purchased.plan.consecutive.count = 0; - user3.purchased.plan.consecutive.offset = 3; - user3.purchased.plan.consecutive.trinkets = 1; - user3.purchased.plan.consecutive.gemCapExtra = 5; + beforeEach(async () => { + user3.purchased.plan.customerId = 'subscribedId'; + user3.purchased.plan.dateUpdated = moment().toDate(); + user3.purchased.plan.planId = 'basic_3mo'; + user3.purchased.plan.perkMonthCount = 0; + user3.purchased.plan.consecutive.count = 0; + user3.purchased.plan.consecutive.offset = 3; + user3.purchased.plan.consecutive.trinkets = 1; + user3.purchased.plan.consecutive.gemCapExtra = 5; + }); it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') @@ -390,6 +438,21 @@ describe('cron', async () => { expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10); }); + it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => { + user3.purchased.plan.perkMonthCount = 2; + user3.purchased.plan.consecutive.trinkets = 1; + user3.purchased.plan.consecutive.gemCapExtra = 5; + clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months') + .add(2, 'days') + .toDate()); + await cron({ + user: user3, tasksByType, daysMissed, analytics, + }); + expect(user3.purchased.plan.perkMonthCount).to.equal(2); + expect(user3.purchased.plan.consecutive.trinkets).to.equal(2); + expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10); + }); + it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(5, 'months') .add(2, 'days') @@ -456,13 +519,16 @@ describe('cron', async () => { }, }); // user6 has a 6-month recurring subscription starting today - user6.purchased.plan.customerId = 'subscribedId'; - user6.purchased.plan.dateUpdated = moment().toDate(); - user6.purchased.plan.planId = 'google_6mo'; - user6.purchased.plan.consecutive.count = 0; - user6.purchased.plan.consecutive.offset = 6; - user6.purchased.plan.consecutive.trinkets = 2; - user6.purchased.plan.consecutive.gemCapExtra = 10; + beforeEach(async () => { + user6.purchased.plan.customerId = 'subscribedId'; + user6.purchased.plan.dateUpdated = moment().toDate(); + user6.purchased.plan.planId = 'google_6mo'; + user6.purchased.plan.perkMonthCount = 0; + user6.purchased.plan.consecutive.count = 0; + user6.purchased.plan.consecutive.offset = 6; + user6.purchased.plan.consecutive.trinkets = 2; + user6.purchased.plan.consecutive.gemCapExtra = 10; + }); it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') @@ -503,6 +569,19 @@ describe('cron', async () => { expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20); }); + it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => { + user6.purchased.plan.perkMonthCount = 2; + clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months') + .add(2, 'days') + .toDate()); + await cron({ + user: user6, tasksByType, daysMissed, analytics, + }); + expect(user6.purchased.plan.perkMonthCount).to.equal(2); + expect(user6.purchased.plan.consecutive.trinkets).to.equal(4); + expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20); + }); + it('increments consecutive benefits the month after the third paid period has started', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months') .add(2, 'days') diff --git a/test/api/unit/libs/payments/apple.test.js b/test/api/unit/libs/payments/apple.test.js index 217a307d0b..2f01fef793 100644 --- a/test/api/unit/libs/payments/apple.test.js +++ b/test/api/unit/libs/payments/apple.test.js @@ -29,8 +29,9 @@ describe('Apple Payments', () => { .resolves(); iapValidateStub = sinon.stub(iap, 'validate') .resolves({}); - iapIsValidatedStub = sinon.stub(iap, 'isValidated') - .returns(true); + iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true); + sinon.stub(iap, 'isExpired').returns(false); + sinon.stub(iap, 'isCanceled').returns(false); iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') .returns([{ productId: 'com.habitrpg.ios.Habitica.21gems', @@ -44,6 +45,8 @@ describe('Apple Payments', () => { iap.setup.restore(); iap.validate.restore(); iap.isValidated.restore(); + iap.isExpired.restore(); + iap.isCanceled.restore(); iap.getPurchaseData.restore(); payments.buySkuItem.restore(); gems.validateGiftMessage.restore(); @@ -218,6 +221,7 @@ describe('Apple Payments', () => { headers = {}; receipt = `{"token": "${token}"}`; nextPaymentProcessing = moment.utc().add({ days: 2 }); + user = new User(); iapSetupStub = sinon.stub(iap, 'setup') .resolves(); @@ -228,14 +232,17 @@ describe('Apple Payments', () => { iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') .returns([{ expirationDate: moment.utc().subtract({ day: 1 }).toDate(), + purchaseDate: moment.utc().valueOf(), productId: sku, transactionId: token, }, { expirationDate: moment.utc().add({ day: 1 }).toDate(), + purchaseDate: moment.utc().valueOf(), productId: 'wrongsku', transactionId: token, }, { expirationDate: moment.utc().add({ day: 1 }).toDate(), + purchaseDate: moment.utc().valueOf(), productId: sku, transactionId: token, }]); @@ -250,21 +257,12 @@ describe('Apple Payments', () => { if (payments.createSubscription.restore) payments.createSubscription.restore(); }); - it('should throw an error if sku is empty', async () => { - await expect(applePayments.subscribe('', user, receipt, headers, nextPaymentProcessing)) - .to.eventually.be.rejected.and.to.eql({ - httpCode: 400, - name: 'BadRequest', - message: i18n.t('missingSubscriptionCode'), - }); - }); - it('should throw an error if receipt is invalid', async () => { iap.isValidated.restore(); iapIsValidatedStub = sinon.stub(iap, 'isValidated') .returns(false); - await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing)) + await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing)) .to.eventually.be.rejected.and.to.eql({ httpCode: 401, name: 'NotAuthorized', @@ -295,13 +293,15 @@ describe('Apple Payments', () => { iap.getPurchaseData.restore(); iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') .returns([{ - expirationDate: moment.utc().add({ day: 1 }).toDate(), + expirationDate: moment.utc().add({ day: 2 }).toDate(), + purchaseDate: new Date(), productId: option.sku, transactionId: token, + originalTransactionId: token, }]); sub = common.content.subscriptionBlocks[option.subKey]; - await applePayments.subscribe(option.sku, user, receipt, headers, nextPaymentProcessing); + await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing); expect(iapSetupStub).to.be.calledOnce; expect(iapValidateStub).to.be.calledOnce; @@ -321,21 +321,253 @@ describe('Apple Payments', () => { nextPaymentProcessing, }); }); + if (option !== subOptions[3]) { + const newOption = subOptions[3]; + it(`upgrades a subscription from ${option.sku} to ${newOption.sku}`, async () => { + const oldSub = common.content.subscriptionBlocks[option.subKey]; + oldSub.logic = 'refundAndRepay'; + user.profile.name = 'sender'; + user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE; + user.purchased.plan.customerId = token; + user.purchased.plan.planId = option.subKey; + user.purchased.plan.additionalData = receipt; + iap.getPurchaseData.restore(); + iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') + .returns([{ + expirationDate: moment.utc().add({ day: 2 }).toDate(), + purchaseDate: moment.utc().valueOf(), + productId: newOption.sku, + transactionId: `${token}new`, + originalTransactionId: token, + }]); + sub = common.content.subscriptionBlocks[newOption.subKey]; + + await applePayments.subscribe(user, + receipt, + headers, + nextPaymentProcessing); + + expect(iapSetupStub).to.be.calledOnce; + expect(iapValidateStub).to.be.calledOnce; + expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt); + expect(iapIsValidatedStub).to.be.calledOnce; + expect(iapIsValidatedStub).to.be.calledWith({}); + expect(iapGetPurchaseDataStub).to.be.calledOnce; + + expect(paymentsCreateSubscritionStub).to.be.calledOnce; + expect(paymentsCreateSubscritionStub).to.be.calledWith({ + user, + customerId: token, + paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, + sub, + headers, + additionalData: receipt, + nextPaymentProcessing, + updatedFrom: oldSub, + }); + }); + } + if (option !== subOptions[0]) { + const newOption = subOptions[0]; + it(`downgrades a subscription from ${option.sku} to ${newOption.sku}`, async () => { + const oldSub = common.content.subscriptionBlocks[option.subKey]; + user.profile.name = 'sender'; + user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE; + user.purchased.plan.customerId = token; + user.purchased.plan.planId = option.subKey; + user.purchased.plan.additionalData = receipt; + iap.getPurchaseData.restore(); + iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') + .returns([{ + expirationDate: moment.utc().add({ day: 2 }).toDate(), + purchaseDate: moment.utc().valueOf(), + productId: newOption.sku, + transactionId: `${token}new`, + originalTransactionId: token, + }]); + sub = common.content.subscriptionBlocks[newOption.subKey]; + + await applePayments.subscribe(user, + receipt, + headers, + nextPaymentProcessing); + + expect(iapSetupStub).to.be.calledOnce; + expect(iapValidateStub).to.be.calledOnce; + expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt); + expect(iapIsValidatedStub).to.be.calledOnce; + expect(iapIsValidatedStub).to.be.calledWith({}); + expect(iapGetPurchaseDataStub).to.be.calledOnce; + + expect(paymentsCreateSubscritionStub).to.be.calledOnce; + expect(paymentsCreateSubscritionStub).to.be.calledWith({ + user, + customerId: token, + paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, + sub, + headers, + additionalData: receipt, + nextPaymentProcessing, + updatedFrom: oldSub, + }); + }); + } }); - it('errors when a user is already subscribed', async () => { - payments.createSubscription.restore(); - user = new User(); - await user.save(); + it('uses the most recent subscription data', async () => { + iap.getPurchaseData.restore(); + iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') + .returns([{ + expirationDate: moment.utc().add({ day: 4 }).toDate(), + purchaseDate: moment.utc().subtract({ day: 5 }).toDate(), + productId: 'com.habitrpg.ios.habitica.subscription.3month', + transactionId: `${token}oldest`, + originalTransactionId: `${token}evenOlder`, + }, { + expirationDate: moment.utc().add({ day: 2 }).toDate(), + purchaseDate: moment.utc().subtract({ day: 1 }).toDate(), + productId: 'com.habitrpg.ios.habitica.subscription.12month', + transactionId: `${token}newest`, + originalTransactionId: `${token}newest`, + }, { + expirationDate: moment.utc().add({ day: 1 }).toDate(), + purchaseDate: moment.utc().subtract({ day: 2 }).toDate(), + productId: 'com.habitrpg.ios.habitica.subscription.6month', + transactionId: token, + originalTransactionId: token, + }]); + sub = common.content.subscriptionBlocks.basic_12mo; - await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing); + await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing); - await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing)) - .to.eventually.be.rejected.and.to.eql({ - httpCode: 401, - name: 'NotAuthorized', - message: applePayments.constants.RESPONSE_ALREADY_USED, - }); + expect(paymentsCreateSubscritionStub).to.be.calledOnce; + expect(paymentsCreateSubscritionStub).to.be.calledWith({ + user, + customerId: `${token}newest`, + paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, + sub, + headers, + additionalData: receipt, + nextPaymentProcessing, + }); + }); + + describe('does not apply multiple times', async () => { + it('errors when a user is using the same subscription', async () => { + payments.createSubscription.restore(); + iap.getPurchaseData.restore(); + iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') + .returns([{ + expirationDate: moment.utc().add({ day: 1 }).toDate(), + purchaseDate: moment.utc().toDate(), + productId: sku, + transactionId: token, + originalTransactionId: token, + }]); + + await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing); + + await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing)) + .to.eventually.be.rejected.and.to.eql({ + httpCode: 401, + name: 'NotAuthorized', + message: applePayments.constants.RESPONSE_ALREADY_USED, + }); + }); + + it('errors when a user is using a rebill of the same subscription', async () => { + user = new User(); + await user.save(); + payments.createSubscription.restore(); + iap.getPurchaseData.restore(); + iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') + .returns([{ + expirationDate: moment.utc().add({ day: 1 }).toDate(), + purchaseDate: moment.utc().toDate(), + productId: sku, + transactionId: `${token}renew`, + originalTransactionId: token, + }]); + + await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing); + + await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing)) + .to.eventually.be.rejected.and.to.eql({ + httpCode: 401, + name: 'NotAuthorized', + message: applePayments.constants.RESPONSE_ALREADY_USED, + }); + }); + + it('errors when a different user is using the subscription', async () => { + user = new User(); + await user.save(); + payments.createSubscription.restore(); + iap.getPurchaseData.restore(); + iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') + .returns([{ + expirationDate: moment.utc().add({ day: 1 }).toDate(), + purchaseDate: moment.utc().toDate(), + productId: sku, + transactionId: token, + originalTransactionId: token, + }]); + + await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing); + + const secondUser = new User(); + await secondUser.save(); + await expect(applePayments.subscribe( + secondUser, receipt, headers, nextPaymentProcessing, + )) + .to.eventually.be.rejected.and.to.eql({ + httpCode: 401, + name: 'NotAuthorized', + message: applePayments.constants.RESPONSE_ALREADY_USED, + }); + }); + + it('errors when a multiple users exist using the subscription', async () => { + user = new User(); + await user.save(); + payments.createSubscription.restore(); + iap.getPurchaseData.restore(); + iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') + .returns([{ + expirationDate: moment.utc().add({ day: 1 }).toDate(), + purchaseDate: moment.utc().toDate(), + productId: sku, + transactionId: token, + originalTransactionId: token, + }]); + + await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing); + const secondUser = new User(); + secondUser.purchased.plan = user.purchased.plan; + secondUser.purchased.plan.dateTerminate = new Date(); + secondUser.save(); + + iap.getPurchaseData.restore(); + iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') + .returns([{ + expirationDate: moment.utc().add({ day: 1 }).toDate(), + purchaseDate: moment.utc().toDate(), + productId: sku, + transactionId: `${token}new`, + originalTransactionId: token, + }]); + + const thirdUser = new User(); + await thirdUser.save(); + await expect(applePayments.subscribe( + thirdUser, receipt, headers, nextPaymentProcessing, + )) + .to.eventually.be.rejected.and.to.eql({ + httpCode: 401, + name: 'NotAuthorized', + message: applePayments.constants.RESPONSE_ALREADY_USED, + }); + }); }); }); @@ -360,9 +592,9 @@ describe('Apple Payments', () => { }); iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') .returns([{ expirationDate: expirationDate.toDate() }]); - iapIsValidatedStub = sinon.stub(iap, 'isValidated') - .returns(true); - + iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true); + sinon.stub(iap, 'isCanceled').returns(false); + sinon.stub(iap, 'isExpired').returns(true); user = new User(); user.profile.name = 'sender'; user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE; @@ -377,6 +609,8 @@ describe('Apple Payments', () => { iap.setup.restore(); iap.validate.restore(); iap.isValidated.restore(); + iap.isExpired.restore(); + iap.isCanceled.restore(); iap.getPurchaseData.restore(); payments.cancelSubscription.restore(); }); @@ -396,6 +630,8 @@ describe('Apple Payments', () => { iap.getPurchaseData.restore(); iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') .returns([{ expirationDate: expirationDate.add({ day: 1 }).toDate() }]); + iap.isExpired.restore(); + sinon.stub(iap, 'isExpired').returns(false); await expect(applePayments.cancelSubscribe(user, headers)) .to.eventually.be.rejected.and.to.eql({ @@ -418,7 +654,38 @@ describe('Apple Payments', () => { }); }); - it('should cancel a user subscription', async () => { + it('should cancel a cancelled subscription with termination date in the future', async () => { + const futureDate = expirationDate.add({ day: 1 }); + iap.getPurchaseData.restore(); + iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') + .returns([{ expirationDate: futureDate }]); + iap.isExpired.restore(); + sinon.stub(iap, 'isExpired').returns(false); + + iap.isCanceled.restore(); + sinon.stub(iap, 'isCanceled').returns(true); + + await applePayments.cancelSubscribe(user, headers); + + expect(iapSetupStub).to.be.calledOnce; + expect(iapValidateStub).to.be.calledOnce; + expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt); + expect(iapIsValidatedStub).to.be.calledOnce; + expect(iapIsValidatedStub).to.be.calledWith({ + expirationDate: futureDate, + }); + expect(iapGetPurchaseDataStub).to.be.calledOnce; + + expect(paymentCancelSubscriptionSpy).to.be.calledOnce; + expect(paymentCancelSubscriptionSpy).to.be.calledWith({ + user, + paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, + nextBill: futureDate.toDate(), + headers, + }); + }); + + it('should cancel an expired subscription', async () => { await applePayments.cancelSubscribe(user, headers); expect(iapSetupStub).to.be.calledOnce; diff --git a/test/api/unit/libs/payments/payments.test.js b/test/api/unit/libs/payments/payments.test.js index 65b42b42f3..697a13fe1b 100644 --- a/test/api/unit/libs/payments/payments.test.js +++ b/test/api/unit/libs/payments/payments.test.js @@ -203,6 +203,28 @@ describe('payments/index', () => { expect(recipient.purchased.plan.dateCreated).to.exist; }); + it('sets plan.dateCurrentTypeCreated if it did not previously exist', async () => { + expect(recipient.purchased.plan.dateCurrentTypeCreated).to.not.exist; + + await api.createSubscription(data); + + expect(recipient.purchased.plan.dateCurrentTypeCreated).to.exist; + }); + + it('keeps plan.dateCreated when changing subscription type', async () => { + await api.createSubscription(data); + const initialDate = recipient.purchased.plan.dateCreated; + await api.createSubscription(data); + expect(recipient.purchased.plan.dateCreated).to.eql(initialDate); + }); + + it('sets plan.dateCurrentTypeCreated when changing subscription type', async () => { + await api.createSubscription(data); + const initialDate = recipient.purchased.plan.dateCurrentTypeCreated; + await api.createSubscription(data); + expect(recipient.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate); + }); + it('does not change plan.customerId if it already exists', async () => { recipient.purchased.plan = plan; data.customerId = 'purchaserCustomerId'; @@ -213,6 +235,116 @@ describe('payments/index', () => { expect(recipient.purchased.plan.customerId).to.eql('customer-id'); }); + it('sets plan.perkMonthCount to 1 if user is not subscribed', async () => { + recipient.purchased.plan = plan; + recipient.purchased.plan.perkMonthCount = 1; + recipient.purchased.plan.customerId = undefined; + data.sub.key = 'basic_earned'; + data.gift.subscription.key = 'basic_earned'; + data.gift.subscription.months = 1; + + expect(recipient.purchased.plan.perkMonthCount).to.eql(1); + await api.createSubscription(data); + + expect(recipient.purchased.plan.perkMonthCount).to.eql(1); + }); + + it('sets plan.perkMonthCount to 1 if field is not initialized', async () => { + recipient.purchased.plan = plan; + recipient.purchased.plan.perkMonthCount = -1; + recipient.purchased.plan.customerId = undefined; + data.sub.key = 'basic_earned'; + data.gift.subscription.key = 'basic_earned'; + data.gift.subscription.months = 1; + + expect(recipient.purchased.plan.perkMonthCount).to.eql(-1); + await api.createSubscription(data); + + expect(recipient.purchased.plan.perkMonthCount).to.eql(1); + }); + + it('sets plan.perkMonthCount to 1 if user had previous count but lapsed subscription', async () => { + recipient.purchased.plan = plan; + recipient.purchased.plan.perkMonthCount = 2; + recipient.purchased.plan.customerId = undefined; + data.sub.key = 'basic_earned'; + data.gift.subscription.key = 'basic_earned'; + data.gift.subscription.months = 1; + + expect(recipient.purchased.plan.perkMonthCount).to.eql(2); + await api.createSubscription(data); + + expect(recipient.purchased.plan.perkMonthCount).to.eql(1); + }); + + it('adds to plan.perkMonthCount if user is already subscribed', async () => { + recipient.purchased.plan = plan; + recipient.purchased.plan.perkMonthCount = 1; + data.sub.key = 'basic_earned'; + data.gift.subscription.key = 'basic_earned'; + data.gift.subscription.months = 1; + + expect(recipient.purchased.plan.perkMonthCount).to.eql(1); + await api.createSubscription(data); + + expect(recipient.purchased.plan.perkMonthCount).to.eql(2); + }); + + it('awards perks if plan.perkMonthCount reaches 3 with existing subscription', async () => { + recipient.purchased.plan = plan; + recipient.purchased.plan.perkMonthCount = 2; + data.sub.key = 'basic_earned'; + data.gift.subscription.key = 'basic_earned'; + data.gift.subscription.months = 1; + + expect(recipient.purchased.plan.perkMonthCount).to.eql(2); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); + await api.createSubscription(data); + + expect(recipient.purchased.plan.perkMonthCount).to.eql(0); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5); + }); + + it('awards perks if plan.perkMonthCount reaches 3 without existing subscription', async () => { + recipient.purchased.plan.perkMonthCount = 0; + expect(recipient.purchased.plan.perkMonthCount).to.eql(0); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); + await api.createSubscription(data); + + expect(recipient.purchased.plan.perkMonthCount).to.eql(0); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5); + }); + + it('awards perks if plan.perkMonthCount reaches 3 without initialized field', async () => { + expect(recipient.purchased.plan.perkMonthCount).to.eql(-1); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); + await api.createSubscription(data); + + expect(recipient.purchased.plan.perkMonthCount).to.eql(0); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5); + }); + + it('awards perks if plan.perkMonthCount goes over 3', async () => { + recipient.purchased.plan = plan; + recipient.purchased.plan.perkMonthCount = 2; + data.sub.key = 'basic_earned'; + + expect(recipient.purchased.plan.perkMonthCount).to.eql(2); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); + await api.createSubscription(data); + + expect(recipient.purchased.plan.perkMonthCount).to.eql(2); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5); + }); + it('sets plan.customerId to "Gift" if it does not already exist', async () => { expect(recipient.purchased.plan.customerId).to.not.exist; @@ -379,6 +511,7 @@ describe('payments/index', () => { expect(user.purchased.plan.customerId).to.eql('customer-id'); expect(user.purchased.plan.dateUpdated).to.exist; expect(user.purchased.plan.gemsBought).to.eql(0); + expect(user.purchased.plan.perkMonthCount).to.eql(0); expect(user.purchased.plan.paymentMethod).to.eql('Payment Method'); expect(user.purchased.plan.extraMonths).to.eql(0); expect(user.purchased.plan.dateTerminated).to.eql(null); @@ -386,6 +519,63 @@ describe('payments/index', () => { expect(user.purchased.plan.dateCreated).to.exist; }); + it('sets plan.dateCreated if it did not previously exist', async () => { + expect(user.purchased.plan.dateCreated).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.dateCreated).to.exist; + }); + + it('sets plan.dateCurrentTypeCreated if it did not previously exist', async () => { + expect(user.purchased.plan.dateCurrentTypeCreated).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.dateCurrentTypeCreated).to.exist; + }); + + it('keeps plan.dateCreated when changing subscription type', async () => { + await api.createSubscription(data); + const initialDate = user.purchased.plan.dateCreated; + await api.createSubscription(data); + expect(user.purchased.plan.dateCreated).to.eql(initialDate); + }); + + it('sets plan.dateCurrentTypeCreated when changing subscription type', async () => { + await api.createSubscription(data); + const initialDate = user.purchased.plan.dateCurrentTypeCreated; + await api.createSubscription(data); + expect(user.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate); + }); + + it('keeps plan.perkMonthCount when changing subscription type', async () => { + await api.createSubscription(data); + user.purchased.plan.perkMonthCount = 2; + await api.createSubscription(data); + expect(user.purchased.plan.perkMonthCount).to.eql(2); + }); + + it('sets plan.perkMonthCount to zero when creating new monthly subscription', async () => { + user.purchased.plan.perkMonthCount = 2; + await api.createSubscription(data); + expect(user.purchased.plan.perkMonthCount).to.eql(0); + }); + + it('sets plan.perkMonthCount to zero when creating new 3 month subscription', async () => { + user.purchased.plan.perkMonthCount = 2; + await api.createSubscription(data); + expect(user.purchased.plan.perkMonthCount).to.eql(0); + }); + + it('updates plan.consecutive.offset when changing subscription type', async () => { + await api.createSubscription(data); + expect(user.purchased.plan.consecutive.offset).to.eql(3); + data.sub.key = 'basic_6mo'; + await api.createSubscription(data); + expect(user.purchased.plan.consecutive.offset).to.eql(6); + }); + it('awards the Royal Purple Jackalope pet', async () => { await api.createSubscription(data); @@ -465,6 +655,89 @@ describe('payments/index', () => { }, }); }); + + context('Upgrades subscription', () => { + it('from basic_earned to basic_6mo', async () => { + data.sub.key = 'basic_earned'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.customerId).to.eql('customer-id'); + const created = user.purchased.plan.dateCreated; + const updated = user.purchased.plan.dateUpdated; + + data.sub.key = 'basic_6mo'; + data.updatedFrom = { key: 'basic_earned' }; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.dateCreated).to.eql(created); + expect(user.purchased.plan.dateUpdated).to.not.eql(updated); + expect(user.purchased.plan.customerId).to.eql('customer-id'); + }); + + it('from basic_3mo to basic_12mo', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_3mo'); + expect(user.purchased.plan.customerId).to.eql('customer-id'); + const created = user.purchased.plan.dateCreated; + const updated = user.purchased.plan.dateUpdated; + + data.sub.key = 'basic_12mo'; + data.updatedFrom = { key: 'basic_3mo' }; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.dateCreated).to.eql(created); + expect(user.purchased.plan.dateUpdated).to.not.eql(updated); + expect(user.purchased.plan.customerId).to.eql('customer-id'); + }); + }); + + context('Downgrades subscription', () => { + it('from basic_6mo to basic_earned', async () => { + data.sub.key = 'basic_6mo'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.customerId).to.eql('customer-id'); + const created = user.purchased.plan.dateCreated; + const updated = user.purchased.plan.dateUpdated; + + data.sub.key = 'basic_earned'; + data.updatedFrom = { key: 'basic_6mo' }; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.dateCreated).to.eql(created); + expect(user.purchased.plan.dateUpdated).to.not.eql(updated); + expect(user.purchased.plan.customerId).to.eql('customer-id'); + }); + + it('from basic_12mo to basic_3mo', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + data.sub.key = 'basic_12mo'; + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.customerId).to.eql('customer-id'); + const created = user.purchased.plan.dateCreated; + const updated = user.purchased.plan.dateUpdated; + + data.sub.key = 'basic_3mo'; + data.updatedFrom = { key: 'basic_12mo' }; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_3mo'); + expect(user.purchased.plan.dateCreated).to.eql(created); + expect(user.purchased.plan.dateUpdated).to.not.eql(updated); + expect(user.purchased.plan.customerId).to.eql('customer-id'); + }); + }); }); context('Block subscription perks', () => { @@ -488,7 +761,6 @@ describe('payments/index', () => { it('adds 10 to plan.consecutive.gemCapExtra for 6 month block', async () => { data.sub.key = 'basic_6mo'; - await api.createSubscription(data); expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); @@ -496,7 +768,6 @@ describe('payments/index', () => { it('adds 20 to plan.consecutive.gemCapExtra for 12 month block', async () => { data.sub.key = 'basic_12mo'; - await api.createSubscription(data); expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); @@ -532,6 +803,532 @@ describe('payments/index', () => { expect(user.purchased.plan.consecutive.trinkets).to.eql(4); }); + + context('Upgrades subscription', () => { + context('Using payDifference logic', () => { + beforeEach(async () => { + data.updatedFrom = { logic: 'payDifference' }; + }); + it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { + data.sub.key = 'basic_earned'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); + + data.sub.key = 'basic_6mo'; + data.updatedFrom.key = 'basic_earned'; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); + }); + + it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_3mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_3mo'; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); + }); + + it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { + data.sub.key = 'basic_earned'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + + data.sub.key = 'basic_6mo'; + data.updatedFrom.key = 'basic_earned'; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + }); + + it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { + data.sub.key = 'basic_6mo'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_6mo'; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + }); + + it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_3mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_3mo'; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + }); + }); + + context('Using payFull logic', () => { + beforeEach(async () => { + data.updatedFrom = { logic: 'payFull' }; + }); + it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { + data.sub.key = 'basic_earned'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); + + data.sub.key = 'basic_6mo'; + data.updatedFrom.key = 'basic_earned'; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); + }); + + it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_3mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_3mo'; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25); + }); + + it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { + data.sub.key = 'basic_earned'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + + data.sub.key = 'basic_6mo'; + data.updatedFrom.key = 'basic_earned'; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + }); + + it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { + data.sub.key = 'basic_6mo'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_6mo'; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(6); + }); + + it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_3mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_3mo'; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(5); + }); + }); + + context('Using refundAndRepay logic', () => { + let clock; + beforeEach(async () => { + clock = sinon.useFakeTimers(new Date('2022-01-01')); + data.updatedFrom = { logic: 'refundAndRepay' }; + }); + context('Upgrades within first half of subscription', () => { + it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { + data.sub.key = 'basic_earned'; + expect(user.purchased.plan.planId).to.not.exist; + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); + + data.sub.key = 'basic_6mo'; + data.updatedFrom.key = 'basic_earned'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2022-01-10')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); + }); + + it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_3mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_3mo'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2022-02-05')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); + }); + + it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { + data.sub.key = 'basic_earned'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + + data.sub.key = 'basic_6mo'; + data.updatedFrom.key = 'basic_earned'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2022-01-08')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + }); + + it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_3mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_3mo'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2022-01-31')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + }); + + it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { + data.sub.key = 'basic_6mo'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_6mo'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2022-01-28')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + }); + + it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => { + data.sub.key = 'basic_earned'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + + data.sub.key = 'basic_6mo'; + data.updatedFrom.key = 'basic_earned'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2024-01-08')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + }); + + it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => { + data.sub.key = 'basic_6mo'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_6mo'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2022-08-28')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + }); + + it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_3mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_3mo'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2022-07-31')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + }); + }); + context('Upgrades within second half of subscription', () => { + it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { + data.sub.key = 'basic_earned'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); + + data.sub.key = 'basic_6mo'; + data.updatedFrom.key = 'basic_earned'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2022-01-20')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); + }); + + it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_3mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_3mo'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2022-02-24')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25); + }); + + it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { + data.sub.key = 'basic_earned'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + + data.sub.key = 'basic_6mo'; + data.updatedFrom.key = 'basic_earned'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2022-01-28')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + }); + + it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { + data.sub.key = 'basic_6mo'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_6mo'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2022-05-28')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(6); + }); + + it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_3mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_3mo'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2022-03-03')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(5); + }); + + it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => { + data.sub.key = 'basic_earned'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + + data.sub.key = 'basic_6mo'; + data.updatedFrom.key = 'basic_earned'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2022-05-28')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + }); + + it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => { + data.sub.key = 'basic_6mo'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_6mo'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2023-05-28')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(6); + }); + + it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_3mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + + data.sub.key = 'basic_12mo'; + data.updatedFrom.key = 'basic_3mo'; + clock.restore(); + clock = sinon.useFakeTimers(new Date('2023-09-03')); + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(5); + }); + }); + afterEach(async () => { + if (clock !== null) clock.restore(); + }); + }); + }); + + context('Downgrades subscription', () => { + it('does not remove from plan.consecutive.gemCapExtra from basic_6mo to basic_earned', async () => { + data.sub.key = 'basic_6mo'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); + + data.sub.key = 'basic_earned'; + data.updatedFrom = { key: 'basic_6mo' }; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); + }); + + it('does not remove from plan.consecutive.gemCapExtra from basic_12mo to basic_3mo', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + data.sub.key = 'basic_12mo'; + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); + + data.sub.key = 'basic_3mo'; + data.updatedFrom = { key: 'basic_12mo' }; + await api.createSubscription(data); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); + }); + + it('does not remove from plan.consecutive.trinkets from basic_6mo to basic_earned', async () => { + data.sub.key = 'basic_6mo'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_6mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + + data.sub.key = 'basic_earned'; + data.updatedFrom = { key: 'basic_6mo' }; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.eql('basic_earned'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + }); + + it('does not remove from plan.consecutive.trinkets from basic_12mo to basic_3mo', async () => { + expect(user.purchased.plan.planId).to.not.exist; + + data.sub.key = 'basic_12mo'; + await api.createSubscription(data); + + expect(user.purchased.plan.planId).to.eql('basic_12mo'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + + data.sub.key = 'basic_3mo'; + data.updatedFrom = { key: 'basic_12mo' }; + await api.createSubscription(data); + expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + }); + }); }); context('Mystery Items', () => { diff --git a/test/api/unit/models/group.test.js b/test/api/unit/models/group.test.js index fd5cea9b05..24cf64a250 100644 --- a/test/api/unit/models/group.test.js +++ b/test/api/unit/models/group.test.js @@ -1359,6 +1359,7 @@ describe('Group Model', () => { describe('#sendChat', () => { beforeEach(() => { sandbox.spy(User, 'update'); + sandbox.spy(User, 'updateMany'); }); it('formats message', () => { @@ -1413,8 +1414,8 @@ describe('Group Model', () => { it('updates users about new messages in party', () => { party.sendChat({ message: 'message' }); - expect(User.update).to.be.calledOnce; - expect(User.update).to.be.calledWithMatch({ + expect(User.updateMany).to.be.calledOnce; + expect(User.updateMany).to.be.calledWithMatch({ 'party._id': party._id, _id: { $ne: '' }, }); @@ -1427,8 +1428,8 @@ describe('Group Model', () => { group.sendChat({ message: 'message' }); - expect(User.update).to.be.calledOnce; - expect(User.update).to.be.calledWithMatch({ + expect(User.updateMany).to.be.calledOnce; + expect(User.updateMany).to.be.calledWithMatch({ guilds: group._id, _id: { $ne: '' }, }); @@ -1437,8 +1438,8 @@ describe('Group Model', () => { it('does not send update to user that sent the message', () => { party.sendChat({ message: 'message', user: { _id: 'user-id', profile: { name: 'user' } } }); - expect(User.update).to.be.calledOnce; - expect(User.update).to.be.calledWithMatch({ + expect(User.updateMany).to.be.calledOnce; + expect(User.updateMany).to.be.calledWithMatch({ 'party._id': party._id, _id: { $ne: 'user-id' }, }); diff --git a/test/api/v3/integration/groups/POST-groups_groupId_join.test.js b/test/api/v3/integration/groups/POST-groups_groupId_join.test.js index 92acf9023f..bbad7a8401 100644 --- a/test/api/v3/integration/groups/POST-groups_groupId_join.test.js +++ b/test/api/v3/integration/groups/POST-groups_groupId_join.test.js @@ -258,47 +258,6 @@ describe('POST /group/:groupId/join', () => { await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 2); }); - it('deletes previous party where the user was the only member', async () => { - const userToInvite = await generateUser(); - const oldParty = await userToInvite.post('/groups', { // add user to a party - name: 'Another Test Party', - type: 'party', - }); - - await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true); - await user.post(`/groups/${party._id}/invite`, { - uuids: [userToInvite._id], - }); - await userToInvite.post(`/groups/${party._id}/join`); - - await expect(user.get('/user')).to.eventually.have.nested.property('party._id', party._id); - await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(false); - }); - - it('does not allow user to leave a party if a quest was active and they were the only member', async () => { - const userToInvite = await generateUser(); - const oldParty = await userToInvite.post('/groups', { // add user to a party - name: 'Another Test Party', - type: 'party', - }); - - await userToInvite.update({ - [`items.quests.${PET_QUEST}`]: 1, - }); - await userToInvite.post(`/groups/${oldParty._id}/quests/invite/${PET_QUEST}`); - - await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true); - await user.post(`/groups/${party._id}/invite`, { - uuids: [userToInvite._id], - }); - - await expect(userToInvite.post(`/groups/${party._id}/join`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageCannotLeaveWhileQuesting'), - }); - }); - it('invites joining member to active quest', async () => { await user.update({ [`items.quests.${PET_QUEST}`]: 1, diff --git a/test/api/v3/integration/groups/POST-groups_invite.test.js b/test/api/v3/integration/groups/POST-groups_invite.test.js index 02f8c5e20c..58a478be00 100644 --- a/test/api/v3/integration/groups/POST-groups_invite.test.js +++ b/test/api/v3/integration/groups/POST-groups_invite.test.js @@ -1,6 +1,7 @@ import { v4 as generateUUID } from 'uuid'; import nconf from 'nconf'; import { + createAndPopulateGroup, generateUser, generateGroup, translate as t, @@ -581,20 +582,7 @@ describe('Post /groups/:groupId/invite', () => { }); }); - it('allow inviting a user to a party if they are partying solo', async () => { - const userToInvite = await generateUser(); - await userToInvite.post('/groups', { // add user to a party - name: 'Another Test Party', - type: 'party', - }); - - await inviter.post(`/groups/${party._id}/invite`, { - uuids: [userToInvite._id], - }); - expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id); - }); - - it('allow inviting a user to 2 different parties', async () => { + it('allows inviting a user to 2 different parties', async () => { // Create another inviter const inviter2 = await generateUser(); @@ -635,29 +623,47 @@ describe('Post /groups/:groupId/invite', () => { }); expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id); }); + }); + + describe('party size limits', () => { + let party, partyLeader; + + beforeEach(async () => { + group = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Party', + type: 'party', + privacy: 'private', + }, + // Generate party with 20 members + members: PARTY_LIMIT_MEMBERS - 10, + }); + party = group.group; + partyLeader = group.groupLeader; + }); it('allows 30 members in a party', async () => { const invitesToGenerate = []; - // Generate 29 users to invite (29 + leader = 30 members) - for (let i = 0; i < PARTY_LIMIT_MEMBERS - 1; i += 1) { + // Generate 10 new invites + for (let i = 1; i < 10; i += 1) { invitesToGenerate.push(generateUser()); } const generatedInvites = await Promise.all(invitesToGenerate); // Invite users - expect(await inviter.post(`/groups/${party._id}/invite`, { + expect(await partyLeader.post(`/groups/${party._id}/invite`, { uuids: generatedInvites.map(invite => invite._id), })).to.be.an('array'); }).timeout(10000); - it('does not allow 30+ members in a party', async () => { + it('does not allow >30 members in a party', async () => { const invitesToGenerate = []; - // Generate 30 users to invite (30 + leader = 31 members) - for (let i = 0; i < PARTY_LIMIT_MEMBERS; i += 1) { + // Generate 11 invites + for (let i = 1; i < 11; i += 1) { invitesToGenerate.push(generateUser()); } const generatedInvites = await Promise.all(invitesToGenerate); // Invite users - await expect(inviter.post(`/groups/${party._id}/invite`, { + await expect(partyLeader.post(`/groups/${party._id}/invite`, { uuids: generatedInvites.map(invite => invite._id), })) .to.eventually.be.rejected.and.eql({ diff --git a/test/api/v3/integration/payments/apple/POST-payments_apple_subscribe.test.js b/test/api/v3/integration/payments/apple/POST-payments_apple_subscribe.test.js index faf5f0e6ea..5a9aa456e7 100644 --- a/test/api/v3/integration/payments/apple/POST-payments_apple_subscribe.test.js +++ b/test/api/v3/integration/payments/apple/POST-payments_apple_subscribe.test.js @@ -45,11 +45,10 @@ describe('payments : apple #subscribe', () => { }); expect(subscribeStub).to.be.calledOnce; - expect(subscribeStub.args[0][0]).to.eql(sku); - expect(subscribeStub.args[0][1]._id).to.eql(user._id); - expect(subscribeStub.args[0][2]).to.eql('receipt'); - expect(subscribeStub.args[0][3]['x-api-key']).to.eql(user.apiToken); - expect(subscribeStub.args[0][3]['x-api-user']).to.eql(user._id); + expect(subscribeStub.args[0][0]._id).to.eql(user._id); + expect(subscribeStub.args[0][1]).to.eql('receipt'); + expect(subscribeStub.args[0][2]['x-api-key']).to.eql(user.apiToken); + expect(subscribeStub.args[0][2]['x-api-user']).to.eql(user._id); }); }); }); diff --git a/test/api/v3/integration/world-state/GET-world-state.test.js b/test/api/v3/integration/world-state/GET-world-state.test.js index ef2f9aca46..cec7086d6f 100644 --- a/test/api/v3/integration/world-state/GET-world-state.test.js +++ b/test/api/v3/integration/world-state/GET-world-state.test.js @@ -35,13 +35,6 @@ describe('GET /world-state', () => { }); }); - it('returns a string representing the current season for NPC sprites', async () => { - const res = await requester().get('/world-state'); - - expect(res).to.have.nested.property('npcImageSuffix'); - expect(res.npcImageSuffix).to.be.a('string'); - }); - context('no current event', () => { beforeEach(async () => { sinon.stub(worldState, 'getCurrentEvent').returns(null); diff --git a/test/common/libs/cron.test.js b/test/common/libs/cron.test.js index d082e1e7b0..e8be92fceb 100644 --- a/test/common/libs/cron.test.js +++ b/test/common/libs/cron.test.js @@ -215,6 +215,7 @@ describe('cron utility functions', () => { it('monthly plan, next date in 3 months', () => { const user = baseUserData(60, 0, 'group_plan_auto'); + user.purchased.plan.perkMonthCount = 0; const planContext = getPlanContext(user, now); @@ -224,6 +225,7 @@ describe('cron utility functions', () => { it('monthly plan, next date in 1 month', () => { const user = baseUserData(62, 0, 'group_plan_auto'); + user.purchased.plan.perkMonthCount = 2; const planContext = getPlanContext(user, now); @@ -248,5 +250,15 @@ describe('cron utility functions', () => { expect(planContext.nextHourglassDate) .to.be.sameMoment('2022-07-10T02:00:00.144Z'); }); + + it('multi-month plan with perk count', () => { + const user = baseUserData(60, 1, 'basic_3mo'); + user.purchased.plan.perkMonthCount = 2; + + const planContext = getPlanContext(user, now); + + expect(planContext.nextHourglassDate) + .to.be.sameMoment('2022-07-10T02:00:00.144Z'); + }); }); }); diff --git a/test/helpers/api-integration/external-server.js b/test/helpers/api-integration/external-server.js index 5b199d058d..5f02eb03c3 100644 --- a/test/helpers/api-integration/external-server.js +++ b/test/helpers/api-integration/external-server.js @@ -12,8 +12,9 @@ const webhookData = {}; app.use(bodyParser.urlencoded({ extended: true, + limit: '10mb', })); -app.use(bodyParser.json()); +app.use(bodyParser.json({ limit: '10mb' })); app.post('/webhooks/:id', (req, res) => { const { id } = req.params; diff --git a/test/helpers/api-integration/requester.js b/test/helpers/api-integration/requester.js index ad01f35978..61c0b2fabf 100644 --- a/test/helpers/api-integration/requester.js +++ b/test/helpers/api-integration/requester.js @@ -53,7 +53,8 @@ function _requestMaker (user, method, additionalSets = {}) { if (user && user._id && user.apiToken) { request .set('x-api-user', user._id) - .set('x-api-key', user.apiToken); + .set('x-api-key', user.apiToken) + .set('x-client', 'habitica-web'); } if (!isEmpty(additionalSets)) { diff --git a/website/client/package-lock.json b/website/client/package-lock.json index 0af1f10a19..8cc351c0ea 100644 --- a/website/client/package-lock.json +++ b/website/client/package-lock.json @@ -1842,9 +1842,9 @@ } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz", - "integrity": "sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", "requires": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", @@ -1870,9 +1870,9 @@ "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" }, "@babel/types": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", - "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz", + "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==", "requires": { "@babel/helper-string-parser": "^7.19.4", "@babel/helper-validator-identifier": "^7.19.1", @@ -4979,9 +4979,9 @@ } }, "@sideway/formula": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", - "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" }, "@sideway/pinpoint": { "version": "2.0.0", @@ -13318,11 +13318,31 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -13354,6 +13374,35 @@ "ansi-regex": "^5.0.1" } }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "vue-loader-v16": { + "version": "npm:vue-loader@16.8.3", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz", + "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==", + "requires": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "loader-utils": "^2.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -16852,9 +16901,9 @@ } }, "core-js": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz", - "integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w==" + "version": "3.30.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.1.tgz", + "integrity": "sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ==" }, "core-js-compat": { "version": "3.11.0", @@ -17903,9 +17952,9 @@ } }, "dompurify": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz", - "integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==" + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz", + "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==" }, "domutils": { "version": "1.7.0", @@ -20652,9 +20701,9 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, "hellojs": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/hellojs/-/hellojs-1.19.5.tgz", - "integrity": "sha512-hFlublej5rHFWjGe6MMMmj78otSIXBbrfvtWPZSSRmXKFoLMFlL41PJ1JPS8xwiSIZca2ve8uHoPZUDwU+/8gg==" + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/hellojs/-/hellojs-1.20.0.tgz", + "integrity": "sha512-zfZGRt0J0OpJHnw2GJz4uh+rzMNAMWpIymiaRbSmCqCvqDMLSx2/qR5JucqnHPsZHUEjyKj5aLJ8K54kyHHjLQ==" }, "hex-color-regex": { "version": "1.1.0", @@ -21964,9 +22013,9 @@ } }, "jquery": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz", - "integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==" + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.4.tgz", + "integrity": "sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ==" }, "js-message": { "version": "1.0.5", @@ -27746,9 +27795,9 @@ } }, "smartbanner.js": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/smartbanner.js/-/smartbanner.js-1.19.1.tgz", - "integrity": "sha512-x3alFTlk6pLuqrm9PrYQv1E+86CrEIgPf/KJ+nP5342BmOWstbdR8OwD3TPmM56zHQm4MEr/eoqbEcfTKdvdKw==" + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/smartbanner.js/-/smartbanner.js-1.19.2.tgz", + "integrity": "sha512-hwcGNp5Hza5PJHTmqP6H8q0XBYhloIQyJgdzv0ldz3HQSeEuKB2riVraQXdKuquE6ZU/0M0yubno53xJ/ZiQQg==" }, "snapdragon": { "version": "0.8.2", @@ -28120,9 +28169,9 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "stopword": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stopword/-/stopword-2.0.5.tgz", - "integrity": "sha512-MgmxgmVs0Uo9G4mMqRc/QBXdPePZUnVNYqnNiv8QdCXTqHmGRw36mrjToCeNxhu/7Ifa7RxAtd/KR/GLZdnN9g==" + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/stopword/-/stopword-2.0.8.tgz", + "integrity": "sha512-btlEC2vEuhCuvshz99hSGsY8GzaP5qzDPQm56j6rR/R38p8xdsOXgU5a6tIgvU/4hcCta1Vlo/2FVXA9m0f8XA==" }, "store2": { "version": "2.10.0", @@ -29770,9 +29819,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==" + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", + "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==" }, "uc.micro": { "version": "1.0.6", @@ -30240,9 +30289,9 @@ } }, "validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", + "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==" }, "vary": { "version": "1.1.2", @@ -30574,76 +30623,6 @@ } } }, - "vue-loader-v16": { - "version": "npm:vue-loader@16.8.3", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz", - "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==", - "requires": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "loader-utils": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "vue-mugen-scroll": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/vue-mugen-scroll/-/vue-mugen-scroll-0.2.6.tgz", diff --git a/website/client/package.json b/website/client/package.json index 746bb82765..3ce61b456f 100644 --- a/website/client/package.json +++ b/website/client/package.json @@ -32,30 +32,30 @@ "bootstrap": "^4.6.0", "bootstrap-vue": "^2.23.1", "chai": "^4.3.7", - "core-js": "^3.27.2", - "dompurify": "^2.4.1", + "core-js": "^3.30.1", + "dompurify": "^2.4.3", "eslint": "^6.8.0", "eslint-config-habitrpg": "^6.2.0", "eslint-plugin-mocha": "^5.3.0", "eslint-plugin-vue": "^6.2.2", "habitica-markdown": "^3.0.0", - "hellojs": "^1.19.5", + "hellojs": "^1.20.0", "inspectpack": "^4.7.1", "intro.js": "^6.0.0", - "jquery": "^3.6.3", + "jquery": "^3.6.4", "lodash": "^4.17.21", "moment": "^2.29.4", "nconf": "^0.12.0", "sass": "^1.34.0", "sass-loader": "^8.0.2", - "smartbanner.js": "^1.19.1", - "stopword": "^2.0.5", + "smartbanner.js": "^1.19.2", + "stopword": "^2.0.8", "svg-inline-loader": "^0.8.2", "svg-url-loader": "^7.1.1", "svgo": "^1.3.2", "svgo-loader": "^2.2.1", "uuid": "^8.3.2", - "validator": "^13.7.0", + "validator": "^13.9.0", "vue": "^2.7.10", "vue-cli-plugin-storybook": "2.1.0", "vue-mugen-scroll": "^0.2.6", @@ -66,6 +66,6 @@ "webpack": "^4.46.0" }, "devDependencies": { - "@babel/plugin-proposal-optional-chaining": "^7.20.7" + "@babel/plugin-proposal-optional-chaining": "^7.21.0" } } diff --git a/website/client/src/app.vue b/website/client/src/app.vue index ede7eb8c9c..95cb2ba425 100644 --- a/website/client/src/app.vue +++ b/website/client/src/app.vue @@ -35,6 +35,7 @@ + @@ -175,6 +176,7 @@ import amazonPaymentsModal from '@/components/payments/amazonModal'; import paymentsSuccessModal from '@/components/payments/successModal'; import subCancelModalConfirm from '@/components/payments/cancelModalConfirm'; import subCanceledModal from '@/components/payments/canceledModal'; +import externalLinkModal from '@/components/externalLinkModal.vue'; import spellsMixin from '@/mixins/spells'; import { @@ -210,6 +212,7 @@ export default { subCanceledModal, bugReportModal, bugReportSuccessModal, + externalLinkModal, }, mixins: [notifications, spellsMixin], data () { diff --git a/website/client/src/assets/css/sprites/spritesmith-main.css b/website/client/src/assets/css/sprites/spritesmith-main.css index e20700e5c5..8a5531760f 100644 --- a/website/client/src/assets/css/sprites/spritesmith-main.css +++ b/website/client/src/assets/css/sprites/spritesmith-main.css @@ -293,6 +293,11 @@ width: 48px; height: 52px; } +.achievement-plantParent2x { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/achievement-plantParent2x.png'); + width: 64px; + height: 68px; +} .achievement-polarPro2x { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/achievement-polarPro2x.png'); width: 68px; @@ -1369,6 +1374,11 @@ width: 141px; height: 147px; } +.background_jungle_watering_hole { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_jungle_watering_hole.png'); + width: 141px; + height: 147px; +} .background_kelp_forest { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_kelp_forest.png'); width: 141px; @@ -1379,6 +1389,11 @@ width: 141px; height: 147px; } +.background_leafy_tree_tunnel { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_leafy_tree_tunnel.png'); + width: 141px; + height: 147px; +} .background_lighthouse_shore { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_lighthouse_shore.png'); width: 141px; @@ -1404,6 +1419,11 @@ width: 141px; height: 147px; } +.background_mangrove_forest { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_mangrove_forest.png'); + width: 141px; + height: 147px; +} .background_marble_temple { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_marble_temple.png'); width: 141px; @@ -1514,6 +1534,11 @@ width: 141px; height: 147px; } +.background_old_timey_basketball_court { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_old_timey_basketball_court.png'); + width: 141px; + height: 147px; +} .background_on_a_castle_wall { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_on_a_castle_wall.png'); width: 141px; @@ -1819,6 +1844,11 @@ width: 141px; height: 147px; } +.background_springtime_shower { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_springtime_shower.png'); + width: 141px; + height: 147px; +} .background_stable { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_stable.png'); width: 141px; @@ -2004,6 +2034,11 @@ width: 141px; height: 147px; } +.background_under_wisteria { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_under_wisteria.png'); + width: 141px; + height: 147px; +} .background_underwater_among_koi { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_underwater_among_koi.png'); width: 141px; @@ -3025,6 +3060,11 @@ width: 68px; height: 68px; } +.icon_background_jungle_watering_hole { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_jungle_watering_hole.png'); + width: 68px; + height: 68px; +} .icon_background_kelp_forest { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_kelp_forest.png'); width: 68px; @@ -3035,6 +3075,11 @@ width: 68px; height: 68px; } +.icon_background_leafy_tree_tunnel { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_leafy_tree_tunnel.png'); + width: 68px; + height: 68px; +} .icon_background_lighthouse_shore { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_lighthouse_shore.png'); width: 68px; @@ -3060,6 +3105,11 @@ width: 68px; height: 68px; } +.icon_background_mangrove_forest { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_mangrove_forest.png'); + width: 68px; + height: 68px; +} .icon_background_marble_temple { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_marble_temple.png'); width: 68px; @@ -3170,6 +3220,11 @@ width: 68px; height: 68px; } +.icon_background_old_timey_basketball_court { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_old_timey_basketball_court.png'); + width: 68px; + height: 68px; +} .icon_background_on_a_castle_wall { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_on_a_castle_wall.png'); width: 68px; @@ -3475,6 +3530,11 @@ width: 68px; height: 68px; } +.icon_background_springtime_shower { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_springtime_shower.png'); + width: 68px; + height: 68px; +} .icon_background_stable { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_stable.png'); width: 68px; @@ -3665,6 +3725,11 @@ width: 68px; height: 68px; } +.icon_background_under_wisteria { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_under_wisteria.png'); + width: 68px; + height: 68px; +} .icon_background_underwater_among_koi { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_underwater_among_koi.png'); width: 68px; @@ -18320,6 +18385,11 @@ width: 90px; height: 90px; } +.broad_armor_armoire_basketballUniform { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_basketballUniform.png'); + width: 114px; + height: 90px; +} .broad_armor_armoire_bathtub { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_bathtub.png'); width: 114px; @@ -18795,6 +18865,11 @@ width: 90px; height: 90px; } +.head_armoire_beaniePropellerHat { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_beaniePropellerHat.png'); + width: 114px; + height: 90px; +} .head_armoire_bigWig { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_bigWig.png'); width: 90px; @@ -19205,6 +19280,11 @@ width: 114px; height: 90px; } +.shield_armoire_basketball { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_basketball.png'); + width: 114px; + height: 90px; +} .shield_armoire_birthdayBanner { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_birthdayBanner.png'); width: 114px; @@ -19575,6 +19655,11 @@ width: 68px; height: 68px; } +.shop_armor_armoire_basketballUniform { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_armoire_basketballUniform.png'); + width: 68px; + height: 68px; +} .shop_armor_armoire_bathtub { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_armoire_bathtub.png'); width: 68px; @@ -20065,6 +20150,11 @@ width: 68px; height: 68px; } +.shop_head_armoire_beaniePropellerHat { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_armoire_beaniePropellerHat.png'); + width: 68px; + height: 68px; +} .shop_head_armoire_bigWig { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_armoire_bigWig.png'); width: 68px; @@ -20475,6 +20565,11 @@ width: 68px; height: 68px; } +.shop_shield_armoire_basketball { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_armoire_basketball.png'); + width: 68px; + height: 68px; +} .shop_shield_armoire_birthdayBanner { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_armoire_birthdayBanner.png'); width: 68px; @@ -21305,6 +21400,11 @@ width: 90px; height: 90px; } +.slim_armor_armoire_basketballUniform { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_basketballUniform.png'); + width: 114px; + height: 90px; +} .slim_armor_armoire_bathtub { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_bathtub.png'); width: 114px; @@ -27750,6 +27850,86 @@ width: 68px; height: 68px; } +.eyewear_mystery_202303 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/eyewear_mystery_202303.png'); + width: 114px; + height: 90px; +} +.head_mystery_202303 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202303.png'); + width: 114px; + height: 90px; +} +.shop_eyewear_mystery_202303 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_eyewear_mystery_202303.png'); + width: 68px; + height: 68px; +} +.shop_head_mystery_202303 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_mystery_202303.png'); + width: 68px; + height: 68px; +} +.shop_set_mystery_202303 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_set_mystery_202303.png'); + width: 68px; + height: 68px; +} +.broad_armor_mystery_202304 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_202304.png'); + width: 114px; + height: 90px; +} +.head_mystery_202304 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202304.png'); + width: 114px; + height: 90px; +} +.set_mystery_202304 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/set_mystery_202304.png'); + width: 68px; + height: 68px; +} +.shop_armor_mystery_202304 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_mystery_202304.png'); + width: 68px; + height: 68px; +} +.shop_head_mystery_202304 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_mystery_202304.png'); + width: 68px; + height: 68px; +} +.slim_armor_mystery_202304 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202304.png'); + width: 114px; + height: 90px; +} +.back_mystery_202305 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_mystery_202305.png'); + width: 114px; + height: 90px; +} +.headAccessory_mystery_202305 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_mystery_202305.png'); + width: 114px; + height: 90px; +} +.shop_back_mystery_202305 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_back_mystery_202305.png'); + width: 68px; + height: 68px; +} +.shop_headAccessory_mystery_202305 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_mystery_202305.png'); + width: 68px; + height: 68px; +} +.shop_set_mystery_202305 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_set_mystery_202305.png'); + width: 68px; + height: 68px; +} .broad_armor_mystery_301404 { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_301404.png'); width: 90px; @@ -28105,6 +28285,26 @@ width: 114px; height: 90px; } +.broad_armor_special_spring2023Healer { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_spring2023Healer.png'); + width: 117px; + height: 120px; +} +.broad_armor_special_spring2023Mage { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_spring2023Mage.png'); + width: 117px; + height: 120px; +} +.broad_armor_special_spring2023Rogue { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_spring2023Rogue.png'); + width: 114px; + height: 90px; +} +.broad_armor_special_spring2023Warrior { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_spring2023Warrior.png'); + width: 117px; + height: 120px; +} .broad_armor_special_springHealer { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_springHealer.png'); width: 90px; @@ -28365,6 +28565,26 @@ width: 114px; height: 90px; } +.head_special_spring2023Healer { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_spring2023Healer.png'); + width: 117px; + height: 120px; +} +.head_special_spring2023Mage { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_spring2023Mage.png'); + width: 117px; + height: 120px; +} +.head_special_spring2023Rogue { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_spring2023Rogue.png'); + width: 114px; + height: 90px; +} +.head_special_spring2023Warrior { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_spring2023Warrior.png'); + width: 117px; + height: 120px; +} .head_special_springHealer { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_springHealer.png'); width: 90px; @@ -28505,6 +28725,21 @@ width: 114px; height: 90px; } +.shield_special_spring2023Healer { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_spring2023Healer.png'); + width: 117px; + height: 120px; +} +.shield_special_spring2023Rogue { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_spring2023Rogue.png'); + width: 114px; + height: 90px; +} +.shield_special_spring2023Warrior { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_spring2023Warrior.png'); + width: 117px; + height: 120px; +} .shield_special_springHealer { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_springHealer.png'); width: 90px; @@ -28680,6 +28915,26 @@ width: 68px; height: 68px; } +.shop_armor_special_spring2023Healer { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_spring2023Healer.png'); + width: 68px; + height: 68px; +} +.shop_armor_special_spring2023Mage { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_spring2023Mage.png'); + width: 68px; + height: 68px; +} +.shop_armor_special_spring2023Rogue { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_spring2023Rogue.png'); + width: 68px; + height: 68px; +} +.shop_armor_special_spring2023Warrior { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_spring2023Warrior.png'); + width: 68px; + height: 68px; +} .shop_armor_special_springHealer { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_springHealer.png'); width: 68px; @@ -28940,6 +29195,26 @@ width: 68px; height: 68px; } +.shop_head_special_spring2023Healer { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_spring2023Healer.png'); + width: 68px; + height: 68px; +} +.shop_head_special_spring2023Mage { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_spring2023Mage.png'); + width: 68px; + height: 68px; +} +.shop_head_special_spring2023Rogue { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_spring2023Rogue.png'); + width: 68px; + height: 68px; +} +.shop_head_special_spring2023Warrior { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_spring2023Warrior.png'); + width: 68px; + height: 68px; +} .shop_head_special_springHealer { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_special_springHealer.png'); width: 68px; @@ -29080,6 +29355,21 @@ width: 68px; height: 68px; } +.shop_shield_special_spring2023Healer { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_special_spring2023Healer.png'); + width: 68px; + height: 68px; +} +.shop_shield_special_spring2023Rogue { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_special_spring2023Rogue.png'); + width: 68px; + height: 68px; +} +.shop_shield_special_spring2023Warrior { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_special_spring2023Warrior.png'); + width: 68px; + height: 68px; +} .shop_shield_special_springHealer { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_special_springHealer.png'); width: 68px; @@ -29255,6 +29545,26 @@ width: 68px; height: 68px; } +.shop_weapon_special_spring2023Healer { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_spring2023Healer.png'); + width: 68px; + height: 68px; +} +.shop_weapon_special_spring2023Mage { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_spring2023Mage.png'); + width: 68px; + height: 68px; +} +.shop_weapon_special_spring2023Rogue { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_spring2023Rogue.png'); + width: 68px; + height: 68px; +} +.shop_weapon_special_spring2023Warrior { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_spring2023Warrior.png'); + width: 68px; + height: 68px; +} .shop_weapon_special_springHealer { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_special_springHealer.png'); width: 68px; @@ -29435,6 +29745,26 @@ width: 114px; height: 90px; } +.slim_armor_special_spring2023Healer { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_spring2023Healer.png'); + width: 117px; + height: 120px; +} +.slim_armor_special_spring2023Mage { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_spring2023Mage.png'); + width: 117px; + height: 120px; +} +.slim_armor_special_spring2023Rogue { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_spring2023Rogue.png'); + width: 114px; + height: 90px; +} +.slim_armor_special_spring2023Warrior { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_spring2023Warrior.png'); + width: 117px; + height: 120px; +} .slim_armor_special_springHealer { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_springHealer.png'); width: 90px; @@ -29615,6 +29945,26 @@ width: 114px; height: 90px; } +.weapon_special_spring2023Healer { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_spring2023Healer.png'); + width: 117px; + height: 120px; +} +.weapon_special_spring2023Mage { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_spring2023Mage.png'); + width: 117px; + height: 120px; +} +.weapon_special_spring2023Rogue { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_spring2023Rogue.png'); + width: 114px; + height: 90px; +} +.weapon_special_spring2023Warrior { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_spring2023Warrior.png'); + width: 117px; + height: 120px; +} .weapon_special_springHealer { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_springHealer.png'); width: 90px; @@ -35473,6 +35823,11 @@ width: 190px; height: 183px; } +.quest_pinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/quest_pinkMarble.png'); + width: 219px; + height: 219px; +} .quest_pterodactyl { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/quest_pterodactyl.png'); width: 219px; @@ -36253,6 +36608,11 @@ width: 68px; height: 68px; } +.inventory_quest_scroll_pinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/inventory_quest_scroll_pinkMarble.png'); + width: 68px; + height: 68px; +} .inventory_quest_scroll_pterodactyl { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/inventory_quest_scroll_pterodactyl.png'); width: 68px; @@ -37603,6 +37963,11 @@ width: 105px; height: 105px; } +.Mount_Body_BearCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_BearCub-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Body_BearCub-Polar { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_BearCub-Polar.png'); width: 105px; @@ -38028,6 +38393,11 @@ width: 105px; height: 105px; } +.Mount_Body_Cactus-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cactus-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Body_Cactus-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Cactus-PolkaDot.png'); width: 105px; @@ -38548,6 +38918,11 @@ width: 105px; height: 105px; } +.Mount_Body_Dragon-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Dragon-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Body_Dragon-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Dragon-PolkaDot.png'); width: 105px; @@ -38968,6 +39343,11 @@ width: 105px; height: 105px; } +.Mount_Body_FlyingPig-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_FlyingPig-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Body_FlyingPig-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_FlyingPig-PolkaDot.png'); width: 105px; @@ -39238,6 +39618,11 @@ width: 105px; height: 105px; } +.Mount_Body_Fox-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Fox-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Body_Fox-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Fox-PolkaDot.png'); width: 105px; @@ -39898,6 +40283,11 @@ width: 105px; height: 105px; } +.Mount_Body_LionCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_LionCub-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Body_LionCub-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_LionCub-PolkaDot.png'); width: 105px; @@ -40388,6 +40778,11 @@ width: 105px; height: 105px; } +.Mount_Body_PandaCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_PandaCub-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Body_PandaCub-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_PandaCub-PolkaDot.png'); width: 105px; @@ -41613,6 +42008,11 @@ width: 105px; height: 105px; } +.Mount_Body_TigerCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_TigerCub-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Body_TigerCub-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_TigerCub-PolkaDot.png'); width: 105px; @@ -42193,6 +42593,11 @@ width: 135px; height: 135px; } +.Mount_Body_Wolf-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Wolf-PinkMarble.png'); + width: 135px; + height: 135px; +} .Mount_Body_Wolf-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Wolf-PolkaDot.png'); width: 135px; @@ -42713,6 +43118,11 @@ width: 105px; height: 105px; } +.Mount_Head_BearCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_BearCub-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Head_BearCub-Polar { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_BearCub-Polar.png'); width: 105px; @@ -43138,6 +43548,11 @@ width: 105px; height: 105px; } +.Mount_Head_Cactus-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cactus-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Head_Cactus-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Cactus-PolkaDot.png'); width: 105px; @@ -43658,6 +44073,11 @@ width: 105px; height: 105px; } +.Mount_Head_Dragon-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Dragon-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Head_Dragon-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Dragon-PolkaDot.png'); width: 105px; @@ -44078,6 +44498,11 @@ width: 105px; height: 105px; } +.Mount_Head_FlyingPig-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_FlyingPig-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Head_FlyingPig-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_FlyingPig-PolkaDot.png'); width: 105px; @@ -44348,6 +44773,11 @@ width: 105px; height: 105px; } +.Mount_Head_Fox-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Fox-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Head_Fox-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Fox-PolkaDot.png'); width: 105px; @@ -45008,6 +45438,11 @@ width: 105px; height: 105px; } +.Mount_Head_LionCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_LionCub-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Head_LionCub-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_LionCub-PolkaDot.png'); width: 105px; @@ -45498,6 +45933,11 @@ width: 105px; height: 105px; } +.Mount_Head_PandaCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_PandaCub-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Head_PandaCub-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_PandaCub-PolkaDot.png'); width: 105px; @@ -46723,6 +47163,11 @@ width: 105px; height: 105px; } +.Mount_Head_TigerCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_TigerCub-PinkMarble.png'); + width: 105px; + height: 105px; +} .Mount_Head_TigerCub-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_TigerCub-PolkaDot.png'); width: 105px; @@ -47303,6 +47748,11 @@ width: 135px; height: 135px; } +.Mount_Head_Wolf-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Wolf-PinkMarble.png'); + width: 135px; + height: 135px; +} .Mount_Head_Wolf-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Wolf-PolkaDot.png'); width: 135px; @@ -47828,6 +48278,11 @@ width: 81px; height: 99px; } +.Mount_Icon_BearCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_BearCub-PinkMarble.png'); + width: 81px; + height: 99px; +} .Mount_Icon_BearCub-Polar { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_BearCub-Polar.png'); width: 81px; @@ -48253,6 +48708,11 @@ width: 81px; height: 99px; } +.Mount_Icon_Cactus-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Cactus-PinkMarble.png'); + width: 81px; + height: 99px; +} .Mount_Icon_Cactus-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Cactus-PolkaDot.png'); width: 81px; @@ -48773,6 +49233,11 @@ width: 81px; height: 99px; } +.Mount_Icon_Dragon-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Dragon-PinkMarble.png'); + width: 81px; + height: 99px; +} .Mount_Icon_Dragon-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Dragon-PolkaDot.png'); width: 81px; @@ -49193,6 +49658,11 @@ width: 81px; height: 99px; } +.Mount_Icon_FlyingPig-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_FlyingPig-PinkMarble.png'); + width: 81px; + height: 99px; +} .Mount_Icon_FlyingPig-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_FlyingPig-PolkaDot.png'); width: 81px; @@ -49463,6 +49933,11 @@ width: 81px; height: 99px; } +.Mount_Icon_Fox-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Fox-PinkMarble.png'); + width: 81px; + height: 99px; +} .Mount_Icon_Fox-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Fox-PolkaDot.png'); width: 81px; @@ -50128,6 +50603,11 @@ width: 81px; height: 99px; } +.Mount_Icon_LionCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_LionCub-PinkMarble.png'); + width: 81px; + height: 99px; +} .Mount_Icon_LionCub-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_LionCub-PolkaDot.png'); width: 81px; @@ -50618,6 +51098,11 @@ width: 81px; height: 99px; } +.Mount_Icon_PandaCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_PandaCub-PinkMarble.png'); + width: 81px; + height: 99px; +} .Mount_Icon_PandaCub-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_PandaCub-PolkaDot.png'); width: 81px; @@ -51843,6 +52328,11 @@ width: 81px; height: 99px; } +.Mount_Icon_TigerCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_TigerCub-PinkMarble.png'); + width: 81px; + height: 99px; +} .Mount_Icon_TigerCub-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_TigerCub-PolkaDot.png'); width: 81px; @@ -52423,6 +52913,11 @@ width: 81px; height: 99px; } +.Mount_Icon_Wolf-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Wolf-PinkMarble.png'); + width: 81px; + height: 99px; +} .Mount_Icon_Wolf-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Wolf-PolkaDot.png'); width: 81px; @@ -52953,6 +53448,11 @@ width: 81px; height: 99px; } +.Pet-BearCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-BearCub-PinkMarble.png'); + width: 81px; + height: 99px; +} .Pet-BearCub-Polar { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-BearCub-Polar.png'); width: 81px; @@ -53053,6 +53553,11 @@ width: 81px; height: 99px; } +.Pet-BearCub-TeaShop { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-BearCub-TeaShop.png'); + width: 81px; + height: 99px; +} .Pet-BearCub-Thunderstorm { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-BearCub-Thunderstorm.png'); width: 81px; @@ -53393,6 +53898,11 @@ width: 81px; height: 99px; } +.Pet-Cactus-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cactus-PinkMarble.png'); + width: 81px; + height: 99px; +} .Pet-Cactus-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cactus-PolkaDot.png'); width: 81px; @@ -53488,6 +53998,11 @@ width: 81px; height: 99px; } +.Pet-Cactus-TeaShop { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cactus-TeaShop.png'); + width: 81px; + height: 99px; +} .Pet-Cactus-Thunderstorm { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Cactus-Thunderstorm.png'); width: 81px; @@ -53933,6 +54448,11 @@ width: 81px; height: 99px; } +.Pet-Dragon-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Dragon-PinkMarble.png'); + width: 81px; + height: 99px; +} .Pet-Dragon-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Dragon-PolkaDot.png'); width: 81px; @@ -54028,6 +54548,11 @@ width: 81px; height: 99px; } +.Pet-Dragon-TeaShop { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Dragon-TeaShop.png'); + width: 81px; + height: 99px; +} .Pet-Dragon-Thunderstorm { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Dragon-Thunderstorm.png'); width: 81px; @@ -54368,6 +54893,11 @@ width: 81px; height: 99px; } +.Pet-FlyingPig-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-FlyingPig-PinkMarble.png'); + width: 81px; + height: 99px; +} .Pet-FlyingPig-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-FlyingPig-PolkaDot.png'); width: 81px; @@ -54463,6 +54993,11 @@ width: 81px; height: 99px; } +.Pet-FlyingPig-TeaShop { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-FlyingPig-TeaShop.png'); + width: 81px; + height: 99px; +} .Pet-FlyingPig-Thunderstorm { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-FlyingPig-Thunderstorm.png'); width: 81px; @@ -54653,6 +55188,11 @@ width: 81px; height: 99px; } +.Pet-Fox-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Fox-PinkMarble.png'); + width: 81px; + height: 99px; +} .Pet-Fox-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Fox-PolkaDot.png'); width: 81px; @@ -54748,6 +55288,11 @@ width: 81px; height: 99px; } +.Pet-Fox-TeaShop { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Fox-TeaShop.png'); + width: 81px; + height: 99px; +} .Pet-Fox-Thunderstorm { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Fox-Thunderstorm.png'); width: 81px; @@ -55333,6 +55878,11 @@ width: 81px; height: 99px; } +.Pet-LionCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-LionCub-PinkMarble.png'); + width: 81px; + height: 99px; +} .Pet-LionCub-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-LionCub-PolkaDot.png'); width: 81px; @@ -55428,6 +55978,11 @@ width: 81px; height: 99px; } +.Pet-LionCub-TeaShop { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-LionCub-TeaShop.png'); + width: 81px; + height: 99px; +} .Pet-LionCub-Thunderstorm { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-LionCub-Thunderstorm.png'); width: 81px; @@ -55838,6 +56393,11 @@ width: 81px; height: 99px; } +.Pet-PandaCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-PandaCub-PinkMarble.png'); + width: 81px; + height: 99px; +} .Pet-PandaCub-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-PandaCub-PolkaDot.png'); width: 81px; @@ -55933,6 +56493,11 @@ width: 81px; height: 99px; } +.Pet-PandaCub-TeaShop { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-PandaCub-TeaShop.png'); + width: 81px; + height: 99px; +} .Pet-PandaCub-Thunderstorm { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-PandaCub-Thunderstorm.png'); width: 81px; @@ -57088,6 +57653,11 @@ width: 81px; height: 99px; } +.Pet-TigerCub-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-TigerCub-PinkMarble.png'); + width: 81px; + height: 99px; +} .Pet-TigerCub-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-TigerCub-PolkaDot.png'); width: 81px; @@ -57183,6 +57753,11 @@ width: 81px; height: 99px; } +.Pet-TigerCub-TeaShop { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-TigerCub-TeaShop.png'); + width: 81px; + height: 99px; +} .Pet-TigerCub-Thunderstorm { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-TigerCub-Thunderstorm.png'); width: 81px; @@ -57688,6 +58263,11 @@ width: 81px; height: 99px; } +.Pet-Wolf-PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Wolf-PinkMarble.png'); + width: 81px; + height: 99px; +} .Pet-Wolf-PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Wolf-PolkaDot.png'); width: 81px; @@ -57783,6 +58363,11 @@ width: 81px; height: 99px; } +.Pet-Wolf-TeaShop { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Wolf-TeaShop.png'); + width: 81px; + height: 99px; +} .Pet-Wolf-Thunderstorm { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Wolf-Thunderstorm.png'); width: 81px; @@ -58023,6 +58608,11 @@ width: 68px; height: 68px; } +.Pet_HatchingPotion_PinkMarble { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_PinkMarble.png'); + width: 68px; + height: 68px; +} .Pet_HatchingPotion_PolkaDot { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_PolkaDot.png'); width: 68px; @@ -58123,6 +58713,11 @@ width: 68px; height: 68px; } +.Pet_HatchingPotion_TeaShop { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_TeaShop.png'); + width: 68px; + height: 68px; +} .Pet_HatchingPotion_Thunderstorm { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Thunderstorm.png'); width: 68px; diff --git a/website/client/src/assets/scss/animals.scss b/website/client/src/assets/scss/animals.scss index 22239d1c5d..0d3407b47f 100644 --- a/website/client/src/assets/scss/animals.scss +++ b/website/client/src/assets/scss/animals.scss @@ -19,8 +19,12 @@ top: -16px !important; } -.Pet.Pet-FlyingPig-Veggie, .Pet.Pet-FlyingPig-Dessert, .Pet.Pet-FlyingPig-VirtualPet { - top: -28px !important; +$foolPets: Veggie, Dessert, VirtualPet, TeaShop; + +@each $foolPet in $foolPets { + .Pet.Pet-FlyingPig-#{$foolPet} { + top: -28px !important; + } } .Pet[class*="Virtual"] { diff --git a/website/client/src/assets/scss/icon.scss b/website/client/src/assets/scss/icon.scss index 92930f1124..3ab036333a 100644 --- a/website/client/src/assets/scss/icon.scss +++ b/website/client/src/assets/scss/icon.scss @@ -24,9 +24,9 @@ } } -.icon-16 { - width: 16px; - height: 16px; +.icon-10 { + width: 10px; + height: 10px; } .icon-12 { @@ -34,21 +34,26 @@ height: 12px; } +.icon-16 { + width: 16px; + height: 16px; +} + .icon-24 { width: 24px; height: 24px; } +.icon-32 { + width: 32px; + height: 32px; +} + .icon-48 { width: 48px; height: 48px; } -.icon-10 { - width: 10px; - height: 10px; -} - .inline { display: inline-block; } diff --git a/website/client/src/assets/scss/markdown.scss b/website/client/src/assets/scss/markdown.scss index c1e517ed78..19f9e45bc1 100644 --- a/website/client/src/assets/scss/markdown.scss +++ b/website/client/src/assets/scss/markdown.scss @@ -50,10 +50,7 @@ h3.markdown { } a { - color: $blue-10; - &:hover, &:active, &:focus { - color: $blue-10; text-decoration: underline; } } diff --git a/website/client/src/assets/scss/typography.scss b/website/client/src/assets/scss/typography.scss index a15f2b7eae..970fd41737 100644 --- a/website/client/src/assets/scss/typography.scss +++ b/website/client/src/assets/scss/typography.scss @@ -26,19 +26,17 @@ a:not([href]), a:not([href]):hover { a, a:not([href]):not([tabindex]) { cursor: pointer; + color: $purple-300; - &.standard-link { - color: $blue-10; + &:hover, &:active, &:focus { + text-decoration: underline; + color: $purple-300; + } - &:hover, &:active, &:focus { - text-decoration: underline; - } - - &[disabled="disabled"] { - color: $gray-300; - text-decoration: none; - cursor: default; - } + &[disabled="disabled"] { + color: $gray-300; + text-decoration: none; + cursor: default; } &.small-link { diff --git a/website/client/src/assets/svg/sync-2.svg b/website/client/src/assets/svg/sync-2.svg new file mode 100644 index 0000000000..61a77879df --- /dev/null +++ b/website/client/src/assets/svg/sync-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/client/src/components/admin-panel/user-support/cronAndAuth.vue b/website/client/src/components/admin-panel/user-support/cronAndAuth.vue index 8b107ebf21..624992ae70 100644 --- a/website/client/src/components/admin-panel/user-support/cronAndAuth.vue +++ b/website/client/src/components/admin-panel/user-support/cronAndAuth.vue @@ -22,6 +22,10 @@ Account created: {{ hero.auth.timestamps.created | formatDate }} +
+ User has employed third party tools. Last known usage: + {{ hero.flags.thirdPartyTools | formatDate }} +
"lastCron" value: {{ hero.lastCron | formatDate }} diff --git a/website/client/src/components/admin-panel/user-support/subscriptionAndPerks.vue b/website/client/src/components/admin-panel/user-support/subscriptionAndPerks.vue index 8fa3cffb0b..9e7ffff117 100644 --- a/website/client/src/components/admin-panel/user-support/subscriptionAndPerks.vue +++ b/website/client/src/components/admin-panel/user-support/subscriptionAndPerks.vue @@ -46,6 +46,10 @@ Perk offset months: {{ hero.purchased.plan.consecutive.offset }}
+
+ Perk month count: + {{ hero.purchased.plan.perkMonthCount }} +
Next Mystic Hourglass: {{ nextHourglassDate }} @@ -149,7 +153,7 @@ export default { nextHourglassDate () { const currentPlanContext = getPlanContext(this.hero, new Date()); - return currentPlanContext.nextHourglassDate.format('MMMM'); + return currentPlanContext.nextHourglassDate.format('MMMM YYYY'); }, }, methods: { diff --git a/website/client/src/components/appFooter.vue b/website/client/src/components/appFooter.vue index 5ba301899a..dbbf211255 100644 --- a/website/client/src/components/appFooter.vue +++ b/website/client/src/components/appFooter.vue @@ -86,6 +86,13 @@ >{{ $t('companyContribute') }} +
  • + {{ $t('translateHabitica') }} + +
  • @@ -101,6 +108,7 @@ v-if="user" > @@ -205,7 +213,7 @@