Compare commits

..

13 Commits

Author SHA1 Message Date
Kalista Payne
40122e5621 5.46.0 2026-02-26 12:08:13 -06:00
Kalista Payne
0ae19d9107 Squashed commit of the following:
commit 963b4133ec
Author: Kalista Payne <kalista@habitica.com>
Date:   Thu Feb 19 15:48:41 2026 -0600

    fix(text): clean up some gear descriptions

commit 53999a5b80
Author: Kalista Payne <kalista@habitica.com>
Date:   Wed Feb 4 17:18:07 2026 -0600

    fix(content): add seasonal set tokens

commit 4510c90e41
Author: Kalista Payne <kalista@habitica.com>
Date:   Wed Feb 4 17:10:24 2026 -0600

    feat(content): March-May 2026
2026-02-24 12:02:52 -06:00
Kalista Payne
68bfebcf30 5.45.0 2026-02-24 10:18:25 -06:00
Phillip Thelen
3e93911e70 Phillip/prod perf2 (#15596)
* Add new api call for kubernetes startup probe

* add hostname as tag for loggly

* Only listen to one change

* increase vite min chunk size

* respond gracefully to shutdown signal

* update server readiness according to mongodb and redis connection

* make larger vite chunks

* fix lint
2026-02-24 10:17:21 -06:00
Kalista Payne
4ea8636f03 fix(profile): correct stat display for class gear 2026-02-20 13:19:56 -06:00
Kalista Payne
9f97a09b8c fix(export): remove deprecated routes 2026-02-19 15:45:38 -06:00
Phillip Thelen
eccc115b73 Admin Panel fixes (#15613)
* fix profile link to admin panel

* fix profile looking broken when no background is equipped
2026-02-19 11:11:25 -06:00
Tanmay Nalawade
2b26eb2bd1 Habit and Daily task counts not displaying for certain filters (#15595)
* habits-all fixed

* dailies section fixed

* to do counter fixed

* Remove linting changes and apply logic fix with single quotes

* refactor(tasks): simpler badgeCount logic

* fix(lint): remove unused import

---------

Co-authored-by: Kalista Payne <kalista@habitica.com>
2026-02-19 11:09:40 -06:00
Kalista Payne
8e042cabc4 5.44.3 2026-02-10 17:37:48 -06:00
Kalista Payne
8abe167848 chore(subproj): update habitica-images 2026-02-10 17:37:45 -06:00
Kalista Payne
3414f962e2 fix(lint): string style and whitespace 2026-02-10 17:13:02 -06:00
Kalista Payne
1b68e6d4d3 fix(event): show Spring Seasonal Shop on Valentines 2026-02-10 17:07:47 -06:00
Kalista Payne
5dd9711413 Update local dev MongoDB versions (#15334)
* chore(mongodb): update local dev MongoDB versions

* fix(github): update mongodb-github-action

* test(api): attempt to gather more detail on failures

* Revert "test(api): attempt to gather more detail on failures"

This reverts commit 215e768e90.

* WIP mongodb-memory-serve

* fix(mongo): start replica set

* run mongo as gh action service

* remove matrix for mongo

* try npm -> docker instead of services

* try "docker compose"

* disable mongo bootstrap from build

* try gh action again

* try newer action version

* working mongo docker compose 🎉

* fix(lint): leave out unused imports

* update lock

* cleanup previous workflow changes

* remove previous code, dont share mongo data folders on runtype (rs and docker)

* mongo docker for testing; align mongodb directory naming

* remove run-rs, add docker:aio script call, use healthcheck to initiate again

* merge docker-compose.yml, fix client port listening

* fix oudated healthcheck param

* chore(mongodb): update local dev MongoDB versions

* fix(github): update mongodb-github-action

* test(api): attempt to gather more detail on failures

* Revert "test(api): attempt to gather more detail on failures"

This reverts commit 215e768e90.

* WIP mongodb-memory-serve

* run mongo as gh action service

* fix(mongo): start replica set

* remove matrix for mongo

* try npm -> docker instead of services

* try "docker compose"

* disable mongo bootstrap from build

* try gh action again

* try newer action version

* working mongo docker compose 🎉

* fix(lint): leave out unused imports

* update lock

* cleanup previous workflow changes

* remove previous code, dont share mongo data folders on runtype (rs and docker)

* mongo docker for testing; align mongodb directory naming

* remove run-rs, add docker:aio script call, use healthcheck to initiate again

* merge docker-compose.yml, fix client port listening

* fix oudated healthcheck param

* fix(config): remove dup keys

* using npx vite during docker aio run

---------

Co-authored-by: negue <eugen.bolz@gmail.com>
2026-01-30 17:19:35 -06:00
41 changed files with 5596 additions and 3524 deletions

View File

@@ -82,7 +82,7 @@ jobs:
CI: true
NODE_ENV: test
- run: npm run test:sanity
common:
runs-on: ubuntu-latest
strategy:
@@ -129,13 +129,13 @@ jobs:
CI: true
NODE_ENV: test
- run: npm run test:content
api-unit:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [21.x]
mongodb-version: [4.2]
mongodb-version: [7.0]
steps:
- uses: actions/checkout@v4
with:
@@ -144,11 +144,13 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
uses: supercharge/mongodb-github-action@1.3.0
uses: supercharge/mongodb-github-action@1.11.0
with:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs
- run: sudo apt update
- run: sudo apt -y install libkrb5-dev
- run: cp config.json.example config.json
@@ -158,15 +160,17 @@ jobs:
env:
CI: true
NODE_ENV: test
- run: npm run test:api:unit
env:
REQUIRES_SERVER=true: true
api-v3-integration:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [21.x]
mongodb-version: [4.2]
mongodb-version: [7.0]
steps:
- uses: actions/checkout@v4
with:
@@ -176,10 +180,11 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
uses: supercharge/mongodb-github-action@1.3.0
uses: supercharge/mongodb-github-action@1.11.0
with:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs
- run: sudo apt update
- run: sudo apt -y install libkrb5-dev
- run: cp config.json.example config.json
@@ -189,15 +194,18 @@ jobs:
env:
CI: true
NODE_ENV: test
- run: npm run test:api-v3:integration
env:
REQUIRES_SERVER=true: true
api-v4-integration:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [21.x]
mongodb-version: [4.2]
mongodb-version: [7.0]
steps:
- uses: actions/checkout@v4
with:
@@ -207,10 +215,11 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
uses: supercharge/mongodb-github-action@1.3.0
uses: supercharge/mongodb-github-action@1.11.0
with:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs
- run: sudo apt update
- run: sudo apt -y install libkrb5-dev
- run: cp config.json.example config.json
@@ -220,6 +229,7 @@ jobs:
env:
CI: true
NODE_ENV: test
- run: npm run test:api-v4:integration
env:
REQUIRES_SERVER=true: true

2
.gitignore vendored
View File

@@ -47,5 +47,5 @@ webpack.webstorm.config
# mongodb replica set for local dev
mongodb-*.tgz
/mongodb-data*
/mongodb-*
/.nyc_output

View File

@@ -46,7 +46,7 @@
"MAINTENANCE_MODE": "false",
"MONGODB_POOL_SIZE": "10",
"MONGODB_SOCKET_TIMEOUT": "20000",
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs&directConnection=true&readPreference=secondary",
"NODE_ENV": "development",
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
"PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo",
@@ -90,7 +90,7 @@
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
"STRIPE_WEBHOOKS_ENDPOINT_SECRET": "111111",
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs&directConnection=true&readPreference=secondary",
"TIME_TRAVEL_ENABLED": "false",
"TRUSTED_DOMAINS": "localhost,https://habitica.com",
"WEB_CONCURRENCY": 1

View File

@@ -1,53 +0,0 @@
services:
client:
build:
context: .
dockerfile: ./Dockerfile-Dev
command: ["npm", "run", "client:dev"]
depends_on:
- server
environment:
- BASE_URL=http://server:3000
networks:
- habitica
ports:
- "8080:8080"
volumes:
- .:/usr/src/habitica
- /usr/src/habitica/node_modules
- /usr/src/habitica/website/client/node_modules
server:
build:
context: .
dockerfile: ./Dockerfile-Dev
command: ["npm", "start"]
depends_on:
mongo:
condition: service_healthy
environment:
- NODE_DB_URI=mongodb://mongo/habitrpg
networks:
- habitica
ports:
- "3000:3000"
volumes:
- .:/usr/src/habitica
- /usr/src/habitica/node_modules
mongo:
image: mongo:5.0.23
restart: unless-stopped
command: ["--replSet", "rs", "--bind_ip_all", "--port", "27017"]
healthcheck:
test: echo "try { rs.status() } catch (err) { rs.initiate() }" | mongosh --port 27017 --quiet
interval: 10s
timeout: 30s
start_period: 0s
start_interval: 1s
retries: 30
networks:
- habitica
ports:
- "27017:27017"
networks:
habitica:
driver: bridge

View File

@@ -0,0 +1,23 @@
networks:
mongodb-network:
name: "mongodb-network"
driver: bridge
services:
mongodb:
image: "mongo:7.0"
container_name: "habitica-mongodb-only"
networks:
- mongodb-network
hostname: "mongodb"
ports:
- "27017:27017"
restart: "unless-stopped"
volumes:
- "./mongodb-data-docker:/data/db"
entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "rs" ]
healthcheck:
test: echo "try { rs.status() } catch (err) { rs.initiate() }" | mongosh --port 27017 --quiet
interval: 10s
timeout: 30s
start_period: 0s
retries: 30

View File

@@ -0,0 +1,23 @@
networks:
mongodb-network:
name: "mongodb-network"
driver: bridge
services:
mongodb:
image: "mongo:7.0"
container_name: "habitica-mongodb-test"
networks:
- mongodb-network
hostname: "mongodb"
ports:
- "27017:27017"
restart: "unless-stopped"
volumes:
- "./mongodb-data-docker-testing:/data/db"
entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "rs" ]
healthcheck:
test: echo "try { rs.status() } catch (err) { rs.initiate() }" | mongosh --port 27017 --quiet
interval: 10s
timeout: 30s
start_period: 0s
retries: 30

View File

@@ -1,35 +1,56 @@
version: "3"
services:
client:
build: .
networks:
- habitica
environment:
- BASE_URL=http://server:3000
ports:
- "8080:8080"
command: ["npm", "run", "client:dev"]
build:
context: .
dockerfile: ./Dockerfile-Dev
command: ["npm", "run", "client:dev:docker"]
depends_on:
- server
server:
build: .
ports:
- "3000:3000"
environment:
- BASE_URL=http://server:3000
networks:
- habitica
ports:
- "5173:5173"
volumes:
- .:/usr/src/habitica
- /usr/src/habitica/node_modules
- /usr/src/habitica/website/client/node_modules
server:
build:
context: .
dockerfile: ./Dockerfile-Dev
command: ["npm", "start"]
depends_on:
mongo:
condition: service_healthy
environment:
- NODE_DB_URI=mongodb://mongo/habitrpg
depends_on:
- mongo
mongo:
image: mongo:3.6
ports:
- "27017:27017"
networks:
- habitica
ports:
- "3000:3000"
volumes:
- .:/usr/src/habitica
- /usr/src/habitica/node_modules
mongo:
image: "mongo:7.0"
container_name: "habitica-mongodb"
networks:
- habitica
hostname: "mongodb"
ports:
- "27017:27017"
restart: "unless-stopped"
volumes:
- "./mongodb-data-docker:/data/db"
entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "rs" ]
healthcheck:
test: echo "try { rs.status() } catch (err) { rs.initiate() }" | mongosh --port 27017 --quiet
interval: 10s
timeout: 30s
start_period: 0s
retries: 30
networks:
habitica:

View File

@@ -5,7 +5,7 @@ import path from 'path';
import babel from 'gulp-babel';
import os from 'os';
import fs from 'fs';
import spawn from 'cross-spawn'; // eslint-disable-line import/no-extraneous-dependencies
import spawn from 'cross-spawn';
import clean from 'rimraf';
gulp.task('build:babel:server', () => gulp.src('website/server/**/*.js')
@@ -35,7 +35,7 @@ gulp.task('build:prod', gulp.series(
// When used on windows `run-rs` must first be run without the `--keep` option
// in order to be setup correctly, afterwards it can be used.
const MONGO_PATH = path.join(__dirname, '/../mongodb-data/');
const MONGO_PATH = path.join(__dirname, '/../mongodb-data-docker/');
gulp.task('build:prepare-mongo', async () => {
if (fs.existsSync(MONGO_PATH)) {
@@ -51,29 +51,32 @@ gulp.task('build:prepare-mongo', async () => {
console.log('MongoDB data folder is missing, setting up.'); // eslint-disable-line no-console
// use run-rs without --keep, kill it as soon as the replica set starts
const runRsProcess = spawn('run-rs', ['-v', '4.1.1', '-l', 'ubuntu1804', '--dbpath', 'mongodb-data', '--number', '1', '--quiet']);
const dockerMongoProcess = spawn('npm', ['run', 'docker:mongo:dev']);
for await (const chunk of runRsProcess.stdout) {
let manuallyStopped = false;
for await (const chunk of dockerMongoProcess.stdout) {
const stringChunk = chunk.toString();
console.log(stringChunk); // eslint-disable-line no-console
// kills the process after the replica set is setup
if (stringChunk.includes('Started replica set')) {
if (stringChunk.includes('mongod startup complete')) {
console.log('MongoDB setup correctly.'); // eslint-disable-line no-console
runRsProcess.kill();
dockerMongoProcess.kill();
manuallyStopped = true;
}
}
let error = '';
for await (const chunk of runRsProcess.stderr) {
for await (const chunk of dockerMongoProcess.stderr) {
const stringChunk = chunk.toString();
error += stringChunk;
}
const exitCode = await new Promise(resolve => {
runRsProcess.on('close', resolve);
dockerMongoProcess.on('close', resolve);
});
if (exitCode || error.length > 0) {
if (!manuallyStopped && (exitCode || error.length > 0)) {
// remove any leftover files
clean.sync(MONGO_PATH);

View File

@@ -53,6 +53,11 @@ gulp.task('test:prepare:mongo', cb => {
const mongooseOptions = getDefaultConnectionOptions();
const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI);
console.info({
mongooseOptions,
connectionUrl,
});
mongoose.connect(connectionUrl, mongooseOptions)
.then(() => mongoose.connection.dropDatabase())
.then(() => mongoose.connection.close()).then(() => {

511
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "habitica",
"version": "5.44.2",
"version": "5.46.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "habitica",
"version": "5.44.2",
"version": "5.46.0",
"hasInstallScript": true,
"dependencies": {
"@babel/core": "^7.22.10",
@@ -97,7 +97,6 @@
"monk": "^7.3.4",
"nyc": "^15.1.0",
"require-again": "^2.0.0",
"run-rs": "^0.7.7",
"sinon-chai": "^3.7.0",
"sinon-stub-promise": "^4.0.0"
},
@@ -3050,9 +3049,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
"integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz",
"integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==",
"license": "MIT",
"dependencies": {
"sparse-bitfield": "^3.0.3"
@@ -6273,7 +6272,7 @@
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"devOptional": true,
"optional": true,
"dependencies": {
"file-uri-to-path": "1.0.0"
}
@@ -6318,12 +6317,6 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
"dev": true
},
"node_modules/body-parser": {
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
@@ -6483,9 +6476,9 @@
}
},
"node_modules/bson": {
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.2.tgz",
"integrity": "sha512-5afhLTjqDSA3akH56E+/2J6kTDuSIlBxyXPdQslj9hcIgOUE378xdOfZvC/9q3LifJNI6KR/juZ+d0NRNYBwXg==",
"version": "6.10.4",
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
"integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==",
"license": "Apache-2.0",
"engines": {
"node": ">=16.20.1"
@@ -7056,16 +7049,6 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
"integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
"dev": true,
"engines": {
"iojs": ">= 1.0.0",
"node": ">= 0.12.0"
}
},
"node_modules/coa": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz",
@@ -7183,15 +7166,6 @@
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="
},
"node_modules/colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
"dev": true,
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/colorspace": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz",
@@ -7749,11 +7723,12 @@
"integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "2.1.2"
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
@@ -7764,6 +7739,12 @@
}
}
},
"node_modules/debug/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
@@ -10596,7 +10577,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"devOptional": true
"optional": true
},
"node_modules/filename-reserved-regex": {
"version": "2.0.0",
@@ -10867,15 +10848,16 @@
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
},
"node_modules/follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
@@ -14399,18 +14381,6 @@
"node": ">=12.0.0"
}
},
"node_modules/kerberos": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/kerberos/-/kerberos-1.1.7.tgz",
"integrity": "sha512-1zXg4rARjsh/VMz2jjZeTfRHbJTVNR6f2DYHbLvtUSOW1satj33Fvc7vOJ0YVWB9+/9ITJWd1QKp4w217SsiFA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"bindings": "^1.5.0",
"nan": "^2.14.1",
"prebuild-install": "6.1.2"
}
},
"node_modules/keygrip": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
@@ -15618,78 +15588,6 @@
"node": ">=18"
}
},
"node_modules/mongodb-core": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.2.7.tgz",
"integrity": "sha512-WypKdLxFNPOH/Jy6i9z47IjG2wIldA54iDZBmHMINcgKOUcWJh8og+Wix76oGd7EyYkHJKssQ2FAOw5Su/n4XQ==",
"dev": true,
"dependencies": {
"bson": "^1.1.1",
"require_optional": "^1.0.1",
"safe-buffer": "^5.1.2"
},
"optionalDependencies": {
"saslprep": "^1.0.0"
}
},
"node_modules/mongodb-core/node_modules/bson": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
"integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==",
"dev": true,
"engines": {
"node": ">=0.6.19"
}
},
"node_modules/mongodb-topology-manager": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mongodb-topology-manager/-/mongodb-topology-manager-2.1.0.tgz",
"integrity": "sha512-s2PelP303LsyJIsReIDUyHhdGPN1xoBY5RynfDKpgT2Wz/D0vaQZN+x0AK6lzj7ro7c8hPFzvHyGA5bJ7JWUug==",
"dev": true,
"dependencies": {
"bluebird": "^3.5.1",
"co": "^4.6.0",
"kerberos": "^1.0.0",
"mkdirp": "^0.5.1",
"mongodb-core": "^3.1.2",
"rimraf": "^2.6.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mongodb-topology-manager/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/mongodb-topology-manager/node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/mongodb/node_modules/bson": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
@@ -16182,24 +16080,6 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw=="
},
"node_modules/node-abi": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz",
"integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==",
"dev": true,
"dependencies": {
"semver": "^5.4.1"
}
},
"node_modules/node-abi/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/node-addon-api": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
@@ -16319,12 +16199,6 @@
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
},
"node_modules/noop-logger": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
"integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==",
"dev": true
},
"node_modules/nopt": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
@@ -17935,167 +17809,6 @@
"underscore": "^1.8.3"
}
},
"node_modules/prebuild-install": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.2.tgz",
"integrity": "sha512-PzYWIKZeP+967WuKYXlTOhYBgGOvTRSfaKI89XnfJ0ansRAH7hDU45X+K+FZeI1Wb/7p/NnuctPH3g0IqKUuSQ==",
"dev": true,
"dependencies": {
"detect-libc": "^1.0.3",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^1.0.1",
"node-abi": "^2.21.0",
"noop-logger": "^0.1.1",
"npmlog": "^4.0.1",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^3.0.3",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/prebuild-install/node_modules/ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prebuild-install/node_modules/aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true
},
"node_modules/prebuild-install/node_modules/are-we-there-yet": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz",
"integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==",
"dev": true,
"dependencies": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
}
},
"node_modules/prebuild-install/node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"dev": true,
"bin": {
"detect-libc": "bin/detect-libc.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/prebuild-install/node_modules/gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==",
"dev": true,
"dependencies": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.0",
"object-assign": "^4.1.0",
"signal-exit": "^3.0.0",
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wide-align": "^1.1.0"
}
},
"node_modules/prebuild-install/node_modules/is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
"dev": true,
"dependencies": {
"number-is-nan": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prebuild-install/node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true
},
"node_modules/prebuild-install/node_modules/npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"dev": true,
"dependencies": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
"gauge": "~2.7.3",
"set-blocking": "~2.0.0"
}
},
"node_modules/prebuild-install/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/prebuild-install/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/prebuild-install/node_modules/string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==",
"dev": true,
"dependencies": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prebuild-install/node_modules/strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
"dev": true,
"dependencies": {
"ansi-regex": "^2.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -18132,19 +17845,6 @@
"node": ">= 0.8"
}
},
"node_modules/prettyjson": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/prettyjson/-/prettyjson-1.2.2.tgz",
"integrity": "sha512-hDso231aQslRQPJjuSMIyUTN5CmW78AwEHlvigOs9E9IO+blW1AJTCJC6pQ8FArBSFsp5ZUdZsWXCUfXiD2D0w==",
"dev": true,
"dependencies": {
"colors": "1.4.0",
"minimist": "^1.2.0"
},
"bin": {
"prettyjson": "bin/prettyjson"
}
},
"node_modules/prismjs": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
@@ -18851,34 +18551,6 @@
"uuid": "bin/uuid"
}
},
"node_modules/require_optional": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
"integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
"dev": true,
"dependencies": {
"resolve-from": "^2.0.0",
"semver": "^5.1.0"
}
},
"node_modules/require_optional/node_modules/resolve-from": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
"integrity": "sha512-qpFcKaXsq8+oRoLilkwyc7zHGF5i9Q2/25NIgLQQ/+VVv9rU4qvr6nXVAw1DsnXJyQkZsR4Ytfbtg5ehfcUssQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require_optional/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/require-again": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-again/-/require-again-2.0.0.tgz",
@@ -19113,104 +18785,6 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/run-rs": {
"version": "0.7.7",
"resolved": "https://registry.npmjs.org/run-rs/-/run-rs-0.7.7.tgz",
"integrity": "sha512-63cLkmCl5JDz642EvtcDvIMG4VEJ7bOI8iJ3ovnydZYPkp8VD02Khr7wcbSs2CoOA9iXdNF27XMZdw+mVLlV2A==",
"dev": true,
"dependencies": {
"chalk": "2.4.1",
"co": "4.6.0",
"commander": "2.15.1",
"moment": "^2.29.2",
"mongodb": "3.6.x",
"mongodb-topology-manager": "2.1.0",
"prettyjson": "1.2.2"
},
"bin": {
"run-rs": "index.js"
}
},
"node_modules/run-rs/node_modules/bson": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
"integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==",
"dev": true,
"engines": {
"node": ">=0.6.19"
}
},
"node_modules/run-rs/node_modules/chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/run-rs/node_modules/commander": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
"dev": true
},
"node_modules/run-rs/node_modules/mongodb": {
"version": "3.6.12",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.12.tgz",
"integrity": "sha512-ErHpF4P4disEIQB8Nns2twIMVXcvmlwjpKqfVnyB/hhd/L5We48LfoBYjBjuUSiSqL6ffmcygPTgjvpy2LETRQ==",
"dev": true,
"dependencies": {
"bl": "^2.2.1",
"bson": "^1.1.4",
"denque": "^1.4.1",
"optional-require": "^1.0.3",
"safe-buffer": "^5.1.2"
},
"engines": {
"node": ">=4"
},
"optionalDependencies": {
"saslprep": "^1.0.0"
},
"peerDependenciesMeta": {
"aws4": {
"optional": true
},
"bson-ext": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"mongodb-extjson": {
"optional": true
},
"snappy": {
"optional": true
}
}
},
"node_modules/run-rs/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/rxjs": {
"version": "6.6.7",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
@@ -19640,41 +19214,6 @@
}
]
},
"node_modules/simple-get": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
"integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
"dev": true,
"dependencies": {
"decompress-response": "^4.2.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/simple-get/node_modules/decompress-response": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
"dev": true,
"dependencies": {
"mimic-response": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/simple-get/node_modules/mimic-response": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
"dev": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "5.44.2",
"version": "5.46.0",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.22.10",
@@ -103,13 +103,16 @@
"coverage": "nyc report --reporter=html --report-dir coverage/results; open coverage/results/index.html",
"sprites": "gulp sprites:compile",
"client:dev": "cd website/client && npm run serve",
"client:dev:docker": "cd website/client && npm run serve:docker",
"client:build": "cd website/client && npm run build",
"client:unit": "cd website/client && npm run test:unit",
"start": "node --watch ./website/server/index.js",
"start:simple": "node ./website/server/index.js",
"debug": "node --watch --inspect ./website/server/index.js",
"mongo:dev": "run-rs -v 7.0.23 -l ubuntu2214 --keep --dbpath mongodb-data --number 1 --quiet",
"mongo:test": "run-rs -v 7.0.23 -l ubuntu2214 --keep --dbpath mongodb-data-testing --number 1 --quiet",
"docker:aio": "docker compose -f docker-compose.yml up",
"docker:mongo:dev": "docker compose -f docker-compose.mongo-only.yml up",
"docker:mongo:dev:down": "docker compose -f docker-compose.mongo-only.yml down",
"docker:mongo:test": "docker compose -f docker-compose.mongo-test-local.yml up",
"mongo:test": "node scripts/start-local-mongo.mjs --test-db",
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
"apidoc": "gulp apidoc",
"heroku-postbuild": ".heroku/report_deploy.sh"
@@ -125,7 +128,6 @@
"monk": "^7.3.4",
"nyc": "^15.1.0",
"require-again": "^2.0.0",
"run-rs": "^0.7.7",
"sinon-chai": "^3.7.0",
"sinon-stub-promise": "^4.0.0"
}

View File

@@ -1,35 +0,0 @@
import { v4 as generateUUID } from 'uuid';
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
xdescribe('GET /export/avatar-:memberId.html', () => {
let user;
before(async () => {
user = await generateUser();
});
it('validates req.params.memberId', async () => {
await expect(user.get('/export/avatar-:memberId.html')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('handles non-existing members', async () => {
const dummyId = generateUUID();
await expect(user.get(`/export/avatar-${dummyId}.html`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('userWithIDNotFound', { userId: dummyId }),
});
});
it('returns an html page', async () => {
const res = await user.get(`/export/avatar-${user._id}.html`);
expect(res.substring(0, 100).indexOf('<!DOCTYPE html>')).to.equal(0);
});
});

View File

@@ -1,3 +0,0 @@
// TODO how to test this route since it points to a file on AWS s3?
describe('GET /export/avatar-:memberId.png', () => {});

View File

@@ -21,6 +21,7 @@ export async function getProperty (collectionName, id, path) {
// Specifically helpful for the GET /groups tests,
// resets the db to an empty state and creates a tavern document
export async function resetHabiticaDB () {
console.info('Resetting Habitica DB');
const groups = mongoose.connection.db.collection('groups');
const users = mongoose.connection.db.collection('users');
return mongoose.connection.dropDatabase()

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@
"private": true,
"scripts": {
"serve": "vite",
"serve:docker": "npx vite --host 0.0.0.0",
"build": "vite build",
"preview": "vite preview",
"test:unit": "vitest run",

View File

@@ -1060,6 +1060,11 @@
width: 141px;
height: 147px;
}
.background_elven_citadel {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_elven_citadel.png');
width: 141px;
height: 147px;
}
.background_enchanted_music_room {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_enchanted_music_room.png');
width: 141px;
@@ -1931,6 +1936,11 @@
width: 141px;
height: 147px;
}
.background_riding_a_comet {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_riding_a_comet.png');
width: 141px;
height: 147px;
}
.background_rime_ice {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_rime_ice.png');
width: 141px;
@@ -2427,6 +2437,11 @@
width: 141px;
height: 147px;
}
.background_waterfall_with_rainbow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_waterfall_with_rainbow.png');
width: 141px;
height: 147px;
}
.background_wedding_arch {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_wedding_arch.png');
width: 141px;
@@ -29800,6 +29815,11 @@
width: 114px;
height: 90px;
}
.broad_armor_armoire_handstandOutfit {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_handstandOutfit.png');
width: 114px;
height: 90px;
}
.broad_armor_armoire_hattersSuit {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_hattersSuit.png');
width: 114px;
@@ -30075,6 +30095,11 @@
width: 114px;
height: 90px;
}
.broad_armor_armoire_softYellowSuit {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_softYellowSuit.png');
width: 114px;
height: 90px;
}
.broad_armor_armoire_springPetalYukata {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_springPetalYukata.png');
width: 114px;
@@ -30385,6 +30410,11 @@
width: 114px;
height: 90px;
}
.head_armoire_floppyYellowHat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_floppyYellowHat.png');
width: 114px;
height: 90px;
}
.head_armoire_flutteryWig {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_flutteryWig.png');
width: 114px;
@@ -30705,6 +30735,11 @@
width: 114px;
height: 90px;
}
.head_armoire_verdantArmingCap {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_verdantArmingCap.png');
width: 114px;
height: 90px;
}
.head_armoire_vermilionArcherHelm {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_vermilionArcherHelm.png');
width: 90px;
@@ -31120,6 +31155,11 @@
width: 114px;
height: 90px;
}
.shield_armoire_softYellowPillow {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_softYellowPillow.png');
width: 114px;
height: 90px;
}
.shield_armoire_spanishGuitar {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_spanishGuitar.png');
width: 114px;
@@ -31170,6 +31210,11 @@
width: 114px;
height: 90px;
}
.shield_armoire_verdantBanner {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_verdantBanner.png');
width: 114px;
height: 90px;
}
.shield_armoire_vikingShield {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_vikingShield.png');
width: 90px;
@@ -31440,6 +31485,11 @@
width: 114px;
height: 90px;
}
.slim_armor_armoire_handstandOutfit {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_handstandOutfit .png');
width: 114px;
height: 90px;
}
.slim_armor_armoire_hattersSuit {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_hattersSuit.png');
width: 114px;
@@ -31715,6 +31765,11 @@
width: 114px;
height: 90px;
}
.slim_armor_armoire_softYellowSuit {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_softYellowSuit.png');
width: 114px;
height: 90px;
}
.slim_armor_armoire_springPetalYukata {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_springPetalYukata.png');
width: 114px;
@@ -34125,11 +34180,21 @@
width: 114px;
height: 90px;
}
.back_mystery_202605 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_mystery_202605.png');
width: 114px;
height: 90px;
}
.broad_armor_mystery_202512 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_202512.png');
width: 114px;
height: 90px;
}
.broad_armor_mystery_202604 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_202604.png');
width: 114px;
height: 90px;
}
.head_mystery_202512 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202512.png');
width: 114px;
@@ -34140,11 +34205,31 @@
width: 114px;
height: 90px;
}
.head_mystery_202603 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202603.png');
width: 114px;
height: 90px;
}
.head_mystery_202604 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202604.png');
width: 114px;
height: 90px;
}
.shield_mystery_202605 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_mystery_202605.png');
width: 114px;
height: 90px;
}
.slim_armor_mystery_202512 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202512.png');
width: 114px;
height: 90px;
}
.slim_armor_mystery_202604 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202604.png');
width: 114px;
height: 90px;
}
.weapon_mystery_202512 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_mystery_202512.png');
width: 114px;
@@ -34155,6 +34240,11 @@
width: 114px;
height: 90px;
}
.weapon_mystery_202603 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_mystery_202603.png');
width: 114px;
height: 90px;
}
.back_mystery_201402 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_mystery_201402.png');
width: 90px;
@@ -36275,6 +36365,26 @@
width: 114px;
height: 90px;
}
.broad_armor_special_spring2026Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_spring2026Healer.png');
width: 114px;
height: 90px;
}
.broad_armor_special_spring2026Mage {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_spring2026Mage.png');
width: 114px;
height: 90px;
}
.broad_armor_special_spring2026Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_spring2026Rogue.png');
width: 114px;
height: 90px;
}
.broad_armor_special_spring2026Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_spring2026Warrior.png');
width: 114px;
height: 90px;
}
.broad_armor_special_springHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_springHealer.png');
width: 90px;
@@ -36595,6 +36705,26 @@
width: 114px;
height: 90px;
}
.head_special_spring2026Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_spring2026Healer.png');
width: 114px;
height: 90px;
}
.head_special_spring2026Mage {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_spring2026Mage.png');
width: 114px;
height: 90px;
}
.head_special_spring2026Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_spring2026Rogue.png');
width: 114px;
height: 90px;
}
.head_special_spring2026Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_spring2026Warrior.png');
width: 114px;
height: 90px;
}
.head_special_springHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_springHealer.png');
width: 90px;
@@ -36780,6 +36910,21 @@
width: 114px;
height: 90px;
}
.shield_special_spring2026Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_spring2026Healer.png');
width: 114px;
height: 90px;
}
.shield_special_spring2026Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_spring2026Rogue.png');
width: 114px;
height: 90px;
}
.shield_special_spring2026Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_spring2026Warrior.png');
width: 114px;
height: 90px;
}
.shield_special_springHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_springHealer.png');
width: 90px;
@@ -37015,6 +37160,26 @@
width: 114px;
height: 90px;
}
.slim_armor_special_spring2026Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_spring2026Healer.png');
width: 114px;
height: 90px;
}
.slim_armor_special_spring2026Mage {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_spring2026Mage.png');
width: 114px;
height: 90px;
}
.slim_armor_special_spring2026Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_spring2026Rogue.png');
width: 114px;
height: 90px;
}
.slim_armor_special_spring2026Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_spring2026Warrior.png');
width: 114px;
height: 90px;
}
.slim_armor_special_springHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_springHealer.png');
width: 90px;
@@ -37255,6 +37420,26 @@
width: 114px;
height: 90px;
}
.weapon_special_spring2026Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_spring2026Healer.png');
width: 114px;
height: 90px;
}
.weapon_special_spring2026Mage {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_spring2026Mage.png');
width: 114px;
height: 90px;
}
.weapon_special_spring2026Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_spring2026Rogue.png');
width: 114px;
height: 90px;
}
.weapon_special_spring2026Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_spring2026Warrior.png');
width: 114px;
height: 90px;
}
.weapon_special_springHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_springHealer.png');
width: 90px;

View File

@@ -498,8 +498,13 @@ export default {
await this.triggerGetWorldState();
this.currentEvent = _find(this.currentEventList, event => Boolean(event.season));
this.imageURLs.background = `url(/static/npc/${this.currentEvent.season}/seasonal_shop_opened_background.png)`;
this.imageURLs.npc = `url(/static/npc/${this.currentEvent.season}/seasonal_shop_opened_npc.png)`;
if (this.currentEvent.season === 'valentines') {
this.imageURLs.background = 'url(/static/npc/spring/seasonal_shop_opened_background.png)';
this.imageURLs.npc = 'url(/static/npc/spring/seasonal_shop_opened_npc.png)';
} else {
this.imageURLs.background = `url(/static/npc/${this.currentEvent.season}/seasonal_shop_opened_background.png)`;
this.imageURLs.npc = `url(/static/npc/${this.currentEvent.season}/seasonal_shop_opened_npc.png)`;
}
},
beforeDestroy () {
this.$root.$off('buyModal::boughtItem');

View File

@@ -348,7 +348,6 @@
import throttle from 'lodash/throttle';
import isEmpty from 'lodash/isEmpty';
import draggable from 'vuedraggable';
import { shouldDo } from '@/../../common/script/cron';
import inAppRewards from '@/../../common/script/libs/inAppRewards';
import taskDefaults from '@/../../common/script/libs/taskDefaults';
import Task from './task';
@@ -482,25 +481,10 @@ export default {
return this.$t('addATask', { type });
},
badgeCount () {
// 0 means the badge will not be shown
// It is shown for the all and due views of dailies
// and for the active and scheduled views of todos.
if (this.type === 'todo' && this.activeFilter.label !== 'complete2') {
return this.taskList.length;
} if (this.type === 'daily') {
if (this.activeFilter.label === 'due') {
return this.taskList.length;
} if (this.activeFilter.label === 'all') {
return this.taskList
.reduce(
(count, t) => (!t.completed
&& shouldDo(new Date(), t, this.getUserPreferences) ? count + 1 : count),
0,
);
}
if (this.type === 'reward') {
return 0;
}
return 0;
return this.taskList.length;
},
},
watch: {

View File

@@ -1340,7 +1340,7 @@ export default {
},
openAdminPanel () {
this.$router.push(`/admin-panel/${this.hero._id}`);
this.$router.push(`/admin/panel/${this.hero._id}`);
},
},
};

View File

@@ -43,7 +43,7 @@
<strong>{{ $t('equipment') }}:</strong>
<span :class="{ 'positive-stat': statsComputed.gearBonus[stat] !== 0 }">
{{ statsComputed.gearBonus[stat] !== 0 ? '+' : '' }}{{
statsComputed.gearBonus[stat]
statsComputed.gearBonus[stat] + statsComputed.classBonus[stat]
}}
</span>
</li>
@@ -246,7 +246,9 @@
:class="{white: user.preferences.background}"
style="overflow:hidden"
>
<Sprite :image-name="'icon_background_' + user.preferences.background" />
<Sprite
v-if="user.preferences.background && user.preferences.background !== ''"
:image-name="'icon_background_' + user.preferences.background" />
</div>
<b-popover
v-if="label !== 'skip'

View File

@@ -122,7 +122,7 @@ export default defineConfig({
},
rollupOptions: {
output: {
experimentalMinChunkSize: 1000
experimentalMinChunkSize: 20000
}
}
},

View File

@@ -1063,6 +1063,18 @@
"backgroundElegantPalaceText": "Elegant Palace",
"backgroundElegantPalaceNotes": "Admire the colorful halls of an Elegant Palace.",
"backgrounds032026": "SET 142: Released March 2026",
"backgroundWaterfallWithRainbowText": "Waterfall with Rainbow",
"backgroundWaterfallWithRainbowNotes": "Admire the breathtaking beauty of a Waterfall with a Rainbow.",
"backgrounds042026": "SET 143: Released April 2026",
"backgroundRidingACometText": "Riding a Comet",
"backgroundRidingACometNotes": "Travel through space while Riding a Comet!",
"backgrounds052026": "SET 144: Released May 2026",
"backgroundElvenCitadelText": "Elven Citadel",
"backgroundElvenCitadelNotes": "Take the scenic journey to an Elven Citadel.",
"timeTravelBackgrounds": "Steampunk Backgrounds",
"backgroundAirshipText": "Airship",
"backgroundAirshipNotes": "Become a sky sailor on board your very own Airship.",

View File

@@ -578,6 +578,15 @@
"weaponSpecialWinter2026MageText": "Candelabra Staff",
"weaponSpecialWinter2026MageNotes": "Candelabras help by holding multiple candles at a time—follow its lead the next time you need to multitask. Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition Winter 2025-2026 Gear.",
"weaponSpecialSpring2026WarriorText": "Mighty Froggy Foil",
"weaponSpecialSpring2026WarriorNotes": "An opportunity to duel might present itself at any moment, and with this formidable foil, you will be ready! Increases Strength by <%= str %>. Limited Edition Spring 2026 Gear.",
"weaponSpecialSpring2026RogueText": "Spring Branch",
"weaponSpecialSpring2026RogueNotes": "An opportunity to grow is nearly upon you, and with these budding branches, you will be ready! Increases Strength by <%= str %>. Limited Edition Spring 2026 Gear.",
"weaponSpecialSpring2026HealerText": "Snowdrop Staff",
"weaponSpecialSpring2026HealerNotes": "An opportunity to begin anew with a fresh start is right up ahead, and with this splendid staff, you will be ready! Increases Intelligence by <%= int %>. Limited Edition Spring 2026 Gear.",
"weaponSpecialSpring2026MageText": "Maypole Parasol",
"weaponSpecialSpring2026MageNotes": "An opportunity to celebrate approaches, and with this pretty parasol pole, you will be ready! Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition Spring 2026 Gear.",
"weaponMystery201411Text": "Pitchfork of Feasting",
"weaponMystery201411Notes": "Stab your enemies or dig in to your favorite foods - this versatile pitchfork does it all! Confers no benefit. November 2014 Subscriber Item.",
"weaponMystery201502Text": "Shimmery Winged Staff of Love and Also Truth",
@@ -626,6 +635,8 @@
"weaponMystery202512Notes": "A shining sword cast from sugar, mint, and arcane enchantments. Confers no benefit. December 2025 Subscriber Item.",
"weaponMystery202601Text": "Winter's Aegis",
"weaponMystery202601Notes": "An icy bubble shield that grants magical protection from opposing elements. Confers no benefit. January 2026 Subscriber Item.",
"weaponMystery202603Text": "Wisteria Wizard Staff",
"weaponMystery202603Notes": "Cast spells to warm the spring air and encourage the blossoms to bud! Confers no benefit. March 2026 Subscriber Item.",
"weaponMystery301404Text": "Steampunk Cane",
"weaponMystery301404Notes": "Excellent for taking a turn about town. March 3015 Subscriber Item. Confers no benefit.",
@@ -1412,6 +1423,15 @@
"armorSpecialWinter2026MageText": "Midwinter Candle Robe",
"armorSpecialWinter2026MageNotes": "Glide smoothly along your path like wax on your way to completing your Dailies. Increases Intelligence by <%= int %>. Limited Edition Winter 2025-2026 Gear.",
"armorSpecialSpring2026WarriorText": "Frog Armor",
"armorSpecialSpring2026WarriorNotes": "Spring into action just as soon as the snow begins to thaw. Increases Constitution by <%= con %>. Limited Edition Spring 2026 Gear.",
"armorSpecialSpring2026RogueText": "Birch Bark Armor",
"armorSpecialSpring2026RogueNotes": "Withstand inevitable spring rains as well as light breezes. Increases Perception by <%= per %>. Limited Edition Spring 2026 Gear.",
"armorSpecialSpring2026HealerText": "Snowdrop Gown",
"armorSpecialSpring2026HealerNotes": "Glide gracefully from a cold, dark winter into glorious spring. Increases Constitution by <%= con %>. Limited Edition Spring 2026 Gear.",
"armorSpecialSpring2026MageText": "Maypole Dancer Outfit",
"armorSpecialSpring2026MageNotes": "Arrive ready to dance, picnic, and enjoy the warm weather spring brings. Increases Intelligence by <%= int %>. Limited Edition Spring 2026 Gear.",
"armorMystery201402Text": "Messenger Robes",
"armorMystery201402Notes": "Shimmering and strong, these robes have many pockets to carry letters. Confers no benefit. February 2014 Subscriber Item.",
"armorMystery201403Text": "Forest Walker Armor",
@@ -1550,6 +1570,8 @@
"armorMystery202509Notes": "Bright silks protect you from the weather, hot or cold. Confers no benefit. September 2025 Subscriber Item.",
"armorMystery202512Text": "Cookie Champion Armor",
"armorMystery202512Notes": "Ready for battle in this plate that is both sweet and strong. Confers no benefit. December 2025 Subscriber Item.",
"armorMystery202604Text": "Audacious Astronaut Spacesuit",
"armorMystery202604Notes": "One small step for your To Do list, one giant leap for your sense of accomplishment! Confers no benefit. April 2026 Subscriber Item.",
"armorMystery301404Text": "Steampunk Suit",
"armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.",
@@ -1800,6 +1822,10 @@
"armorArmoireBlackPartyDressNotes": "Youre strong, smart, hearty, and so fashionable! Increases Strength, Intelligence, and Constitution by <%= attrs %> each. Enchanted Armoire: Black Hairbow Set (Item 2 of 2).",
"armorArmoireLoneCowpokeOutfitText": "Lone Cowpoke Outfit",
"armorArmoireLoneCowpokeOutfitNotes": "Whoa, there! Want to make a statement when you ride into town as a mysterious stranger ready to be productive? Heres the perfect outfit, complete with chaps and a shining, silver belt buckle. Increases Constitution by <%= con %>. Enchanted Armoire: Lone Cowpoke Set (Item 2 of 2)",
"armorArmoireSoftYellowSuitText": "Soft Yellow Suit",
"armorArmoireSoftYellowSuitNotes": "Yellow is an energetic color. Wear this to bed, and you will wake up with the sun the next morning ready to tackle a day full of tasks. Increases Constitution and Strength by <%= attrs %> each. Enchanted Armoire: Yellow Loungewear Set (Item 2 of 3).",
"armorArmoireHandstandOutfitText": "Handstand",
"armorArmoireHandstandOutfitNotes": "Things sure do look different when youre upside-down, dont they? If youre feeling stuck, its time for a fresh perspective! Increases Perception by <%= per %>. Enchanted Armoire: Handstand Set (Item 1 of 1).",
"headgear": "helm",
"headgearCapitalized": "Headgear",
@@ -2352,6 +2378,15 @@
"headSpecialWinter2026MageText": "Midwinter Candle Hat",
"headSpecialWinter2026MageNotes": "Maintain focus and illumination as you set your sights on greater goals this season. Increases Perception by <%= per %>. Limited Edition 2025-2026 Winter Gear.",
"headSpecialSpring2026WarriorText": "Frog Warrior Helm",
"headSpecialSpring2026WarriorNotes": "Frogs are well-known for their resistance to corruption. This helm will grant you their noble qualities! Increases Strength by <%= str %>. Limited Edition Spring 2026 Gear.",
"headSpecialSpring2026RogueText": "Spring Branch Helm",
"headSpecialSpring2026RogueNotes": "Make a striking statement with twigs and buds growing wild in all directions. Increases Perception by <%= per %>. Limited Edition Spring 2026 Gear.",
"headSpecialSpring2026HealerText": "Snowdrop Helm",
"headSpecialSpring2026HealerNotes": "Make a hopeful statement with these beautiful, resilient petals. Increases Intelligence by <%= int %>. Limited Edition Spring 2026 Gear.",
"headSpecialSpring2026MageText": "Mayflower Crown",
"headSpecialSpring2026MageNotes": "Make a joyous statement with bright blooms encircling your head. Increases Perception by <%= per %>. Limited Edition Spring 2026 Gear.",
"headSpecialGaymerxText": "Rainbow Warrior Helm",
"headSpecialGaymerxNotes": "In celebration of the GaymerX Conference, this special helmet is decorated with a radiant, colorful rainbow pattern! GaymerX is a game convention celebrating LGTBQ and gaming and is open to everyone.",
@@ -2538,7 +2573,11 @@
"headMystery202512Text": "Cookie Champion Helm",
"headMystery202512Notes": "Gingerbread forged with ancient magic will protect you as long as you can hold off your urge to try a bite! Confers no benefit. December 2025 Subscriber Item.",
"headMystery202602Text": "Sakura Fox Ears",
"headMystery202602Notes": " Your hearing will be sharpened by these ears such that you can hear the buds of blossoms growing on tree branches as spring approaches. Confers no benefit. February 2026 Subscriber Item.",
"headMystery202602Notes": "Your hearing will be sharpened by these ears such that you can hear the buds of blossoms growing on tree branches as spring approaches. Confers no benefit. February 2026 Subscriber Item.",
"headMystery202603Text": "Wisteria Wizard Hat",
"headMystery202603Notes": "This jaunty hat not only enhances your magical ability, it also has a lovely spring scent! Confers no benefit. March 2026 Subscriber Item.",
"headMystery202604Text": "Audacious Astronaut Helmet",
"headMystery202604Notes": "In space, no one can hear you check off your To Dos. But the real reward is your sense of personal accomplishment! Confers no benefit. April 2026 Subscriber Item.",
"headMystery301404Text": "Fancy Top Hat",
"headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.",
@@ -2769,6 +2808,10 @@
"headArmoireBlacksmithsGogglesNotes": "Shatter and heat-resistant ocular protection is yours when youre working in a forge. Increases Perception by <%= per %>. Enchanted Armoire: Blacksmith Set (Item 1 of 3).",
"headArmoireLoneCowpokeHatText": "Lone Cowpoke Hat",
"headArmoireLoneCowpokeHatNotes": "Howdy there, pardner! Dyou hate when youre out on the range, workin on tasks, and sun gets in your eyes? Well, good thing youve got a hat for that now. Increases Perception by <%= per %>. Enchanted Armoire: Lone Cowpoke Set (Item 1 of 2)",
"headArmoireFloppyYellowHatText": "Yellow Floppy Hat",
"headArmoireFloppyYellowHatNotes": "Many spells have been sewn into this simple hat, giving it a youthful yellow color. Increases all stats by <%= attrs %> each. Enchanted Armoire: Yellow Loungewear Set (Item 1 of 3).",
"headArmoireVerdantArmingCapText": "Verdant Page Arming Cap",
"headArmoireVerdantArmingCapNotes": "This comfy, cushioned coif makes you battle-ready and helps you withstand anything heavy that could come your way. Increases Perception and Constitution by <%= attrs %> each. Enchanted Armoire: Verdant Page Set (Item 1 of 2).",
"offhand": "off-hand item",
"offHandCapitalized": "Off-Hand Item",
@@ -3086,6 +3129,13 @@
"shieldSpecialWinter2026HealerText": "Starburst",
"shieldSpecialWinter2026HealerNotes": "Stars help with wayfinding, energy, and illumination—all things that help you better conquer a task list. Increases Constitution by <%= con %>. Limited Edition Winter 2025-2026 Gear.",
"shieldSpecialSpring2026WarriorText": "Frog Warrior Candelabra",
"shieldSpecialSpring2026WarriorNotes": "Not only can this candelabra light your way, you can use it to melt any lingering snow and ice. Increases Constitution by <%= con %>. Limited Edition Spring 2026 Gear.",
"shieldSpecialSpring2026RogueText": "Spring Branch",
"shieldSpecialSpring2026RogueNotes": "Reach out and reach high with these branches. They double as a back scratcher in a pinch. Increases Strength by <%= str %>. Limited Edition Spring 2026 Gear.",
"shieldSpecialSpring2026HealerText": "Snowdrop Leaf",
"shieldSpecialSpring2026HealerNotes": "Create a light breeze with this fan as the days grow warmer. It doubles as a writing utensil in a pinch. Increases Constitution by <%= con %>. Limited Edition Spring 2026 Gear.",
"shieldMystery201601Text": "Resolution Slayer",
"shieldMystery201601Notes": "This blade can be used to parry away all distractions. Confers no benefit. January 2016 Subscriber Item.",
"shieldMystery201701Text": "Time-Freezer Shield",
@@ -3116,6 +3166,8 @@
"shieldMystery202508Notes": "If you thought one spinning blade was cool looking, try two! Confers no benefit. August 2025 Subscriber Item.",
"shieldMystery202511Text": "Frost Shield",
"shieldMystery202511Notes": "This rugged shield of icy rock protects you from bad Habits but won't freeze your hands. Confers no benefit. November 2025 Subscriber Item.",
"shieldMystery202605Text": "Nightfall Shield",
"shieldMystery202605Notes": "Let the moons shining light protect you from dangers in the dark. Confers no benefit. May 2026 Subscriber Item.",
"shieldMystery301405Text": "Clock Shield",
"shieldMystery301405Notes": "Time is on your side with this towering clock shield! Confers no benefit. June 3015 Subscriber Item.",
@@ -3298,6 +3350,10 @@
"shieldArmoireDoubleBassNotes": "Bom doo bom brrrr brr brr brrrr! Gather your party for some grounding or dancing as you listen to music on this deep double bass. Increases Constitution and Strength by <%= attrs %> each. Enchanted Armoire: Musical Instrument Set 2 (Item 3 of 3)",
"shieldArmoirePrettyPinkGiftBoxText": "Pretty Pink Present",
"shieldArmoirePrettyPinkGiftBoxNotes": "Is this gift from a dear friend? A caring relative? A true love? A secret admirer? Whoever sent it knows youll be pleased with whats inside. Increases all stats by <%= attrs %> each. Enchanted Armoire: Pretty in Pink Set (Item 2 of 2)",
"shieldArmoireSoftYellowPillowText": "Soft Yellow Pillow",
"shieldArmoireSoftYellowPillowNotes": "The experienced warrior packs a pillow for any expedition. Grow and shine as you consolidate all youve learned during past adventures… even while you nap. Increases Intelligence and Perception by <%= attrs %> each. Enchanted Armoire: Yellow Loungewear Set (Item 3 of 3).",
"shieldArmoireVerdantBannerText": "Verdant Page Banner",
"shieldArmoireVerdantBannerNotes": "Wave your banner high to signal friends its time to rally together! Intelligence by <%= int %>. Enchanted Armoire: Verdant Page Set (Item 2 of 2).",
"back": "Back Accessory",
"backBase0Text": "No Back Accessory",
@@ -3392,6 +3448,8 @@
"backMystery202601Notes": "This mark grants the user control over the elements of the season of cold and frost. Confers no benefit. January 2026 Subscriber Item.",
"backMystery202602Text": "Five Tails of Sakura",
"backMystery202602Notes": "These fluffy tails are the color of cherry blossoms, a reminder that spring is on the way! Confers no benefit. February 2026 Subscriber Item.",
"backMystery202605Text": "Nightfall Nimbus",
"backMystery202605Notes": "A glowing aureole of moonlight and starlight to illuminate the darkest night. Confers no benefit. May 2026 Subscriber Item.",
"backArmoireHarpsichordText": "Harpsichord",
"backArmoireHarpsichordNotes": "Pting! Ptiiing! Gather your party for a dinner or picnic and listen to a tinny melody on this harpsichord. Increases Perception and Intelligence by <%= attrs %> each. Enchanted Armoire: Musical Instrument Set 2 (Item 1 of 3)",

View File

@@ -223,26 +223,30 @@
"fall2024UnderworldSorcerorMageSet": "Underworld Sorceror Set (Mage)",
"fall2024SpaceInvaderHealerSet": "Space Invader Set (Healer)",
"fall2024BlackCatRogueSet": "Black Cat Set (Rogue)",
"winter2025MooseWarriorSet": "Moose Warrior Set",
"winter2025AuroraMageSet": "Aurora Mage Set",
"winter2025StringLightsHealerSet": "String Lights Healer Set",
"winter2025SnowRogueSet": "Snow Rogue Set",
"spring2025SunshineWarriorSet": "Sunshine Warrior Set",
"spring2025CrystalPointRogueSet": "Crystal Point Rogue Set",
"spring2025PlumeriaHealerSet": "Plumeria Healer Set",
"spring2025MantisMageSet": "Mantis Mage Set",
"summer2025ScallopWarriorSet": "Scallop Warrior Set",
"summer2025SquidRogueSet": "Squid Rogue Set",
"summer2025SeaAngelHealerSet": "Sea Angel Healer Set",
"summer2025FairyWrasseMageSet": "Fairy Wrasse Mage Set",
"fall2025SasquatchWarriorSet": "Sasquatch Warrior Set",
"fall2025SkeletonRogueSet": "Skeleton Rogue Set",
"fall2025KoboldHealerSet": "Kobold Healer Set",
"fall2025MaskedGhostMageSet": "Masked Ghost Mage Set",
"winter2026RimeReaperWarriorSet": "Rime Reaper Warrior Set",
"winter2026SkiRogueSet": "Ski Rogue Set",
"winter2026PolarBearHealerSet": "Polar Bear Healer Set",
"winter2026MidwinterCandleMageSet": "Midwinter Candle Mage Set",
"winter2025MooseWarriorSet": "Moose Set (Warrior)",
"winter2025AuroraMageSet": "Aurora Set (Mage)",
"winter2025StringLightsHealerSet": "String Lights Set (Healer)",
"winter2025SnowRogueSet": "Snow Set (Rogue)",
"spring2025SunshineWarriorSet": "Sunshine Set (Warrior)",
"spring2025CrystalPointRogueSet": "Crystal Point Set (Rogue)",
"spring2025PlumeriaHealerSet": "Plumeria Set (Healer)",
"spring2025MantisMageSet": "Mantis Set (Mage)",
"summer2025ScallopWarriorSet": "Scallop Set (Warrior)",
"summer2025SquidRogueSet": "Squid Set (Rogue)",
"summer2025SeaAngelHealerSet": "Sea Angel Set (Healer)",
"summer2025FairyWrasseMageSet": "Fairy Wrasse Set (Mage)",
"fall2025SasquatchWarriorSet": "Sasquatch Set (Warrior)",
"fall2025SkeletonRogueSet": "Skeleton Set (Rogue)",
"fall2025KoboldHealerSet": "Kobold Set (Healer)",
"fall2025MaskedGhostMageSet": "Masked Ghost Set (Mage)",
"winter2026RimeReaperWarriorSet": "Rime Reaper Set (Warrior)",
"winter2026SkiRogueSet": "Ski Set (Rogue)",
"winter2026PolarBearHealerSet": "Polar Bear Set (Healer)",
"winter2026MidwinterCandleMageSet": "Midwinter Candle Set (Mage)",
"spring2026FrogWarriorSet": "Frog Set (Warrior)",
"spring2026BranchRogueSet": "Spring Branch Set (Rogue)",
"spring2026SnowdropHealerSet": "Snowdrop Set (Healer)",
"spring2026MaypoleMageSet": "Maypole Set (Mage)",
"winterPromoGiftHeader": "GIFT A SUBSCRIPTION, GET ONE FREE!",
"winterPromoGiftDetails1": "Until January 6th only, when you gift somebody a subscription, you get the same subscription for yourself for free!",
"winterPromoGiftDetails2": "Please note that if you or your gift recipient already have a recurring subscription, the gifted subscription will only start after that subscription is cancelled or has expired. Thanks so much for your support! <3",

View File

@@ -183,6 +183,9 @@
"mysterySet202512": "Cookie Champion Set",
"mysterySet202601": "Winter's Aegis Set",
"mysterySet202602": "Sakura Fox Set",
"mysterySet202603": "Wisteria Wizard Set",
"mysterySet202604": "Audacious Astronaut Set",
"mysterySet202605": "Nightfall Nimbus Set",
"mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set",
"mysterySet301703": "Peacock Steampunk Set",

View File

@@ -683,6 +683,15 @@ const backgrounds = {
backgrounds022026: {
elegant_palace: { },
},
backgrounds032026: {
waterfall_with_rainbow: { },
},
backgrounds042026: {
riding_a_comet: { },
},
backgrounds052026: {
elven_citadel: { },
},
eventBackgrounds: {
birthday_bash: {
price: 0,

View File

@@ -29,6 +29,9 @@ export const ARMOIRE_RELEASE_DATES = {
musicalInstrumentTwo: { year: 2025, month: 12 },
loneCowpoke: { year: 2026, month: 1 },
prettyInPink: { year: 2026, month: 2 },
yellowLoungewear: { year: 2026, month: 3 },
handstand: { year: 2026, month: 4 },
verdantPage: { year: 2026, month: 5 },
};
export const EGGS_RELEASE_DATES = {

View File

@@ -131,6 +131,11 @@ const SEASONAL_SETS = {
'spring2025CrystalPointRogueSet',
'spring2025PlumeriaHealerSet',
'spring2025MantisMageSet',
'spring2026FrogWarriorSet',
'spring2026BranchRogueSet',
'spring2026SnowdropHealerSet',
'spring2026MaypoleMageSet',
],
summer: [

View File

@@ -561,6 +561,15 @@ const armor = {
con: 10,
set: 'loneCowpoke',
},
softYellowSuit: {
con: 9,
str: 9,
set: 'yellowLoungewear',
},
handstandOutfit: {
per: 10,
set: 'handstand',
},
};
const back = {
@@ -1156,6 +1165,18 @@ const head = {
per: 10,
set: 'loneCowpoke',
},
floppyYellowHat: {
con: 3,
int: 3,
per: 3,
str: 3,
set: 'yellowLoungewear',
},
verdantArmingCap: {
con: 5,
per: 5,
set: 'verdantPage',
},
};
const shield = {
@@ -1546,6 +1567,15 @@ const shield = {
str: 2,
set: 'prettyInPink',
},
softYellowPillow: {
int: 9,
per: 9,
set: 'yellowLoungewear',
},
verdantBanner: {
int: 10,
set: 'verdantPage',
},
};
const headAccessory = {

View File

@@ -72,6 +72,7 @@ const armor = {
202504: { },
202509: { },
202512: { },
202604: { },
301404: { },
301703: { },
301704: { },
@@ -122,6 +123,7 @@ const back = {
202510: { },
202601: { },
202602: { },
202605: { },
};
const body = {
@@ -254,6 +256,8 @@ const head = {
202507: { },
202512: { },
202602: { },
202603: { },
202604: { },
301404: { },
301405: { },
301703: { },
@@ -308,6 +312,7 @@ const shield = {
202506: { },
202508: { },
202511: { },
202605: { },
301405: { },
301704: { },
};
@@ -337,6 +342,7 @@ const weapon = {
202511: { },
202512: { },
202601: { },
202603: { },
301404: { },
};

View File

@@ -839,6 +839,18 @@ const armor = {
winter2026Rogue: {
set: 'winter2026SkiRogueSet',
},
spring2026Warrior: {
set: 'spring2026FrogWarriorSet',
},
spring2026Rogue: {
set: 'spring2026BranchRogueSet',
},
spring2026Healer: {
set: 'spring2026SnowdropHealerSet',
},
spring2026Mage: {
set: 'spring2026MaypoleMageSet',
},
};
const armorStats = {
@@ -1988,6 +2000,18 @@ const head = {
winter2026Rogue: {
set: 'winter2026SkiRogueSet',
},
spring2026Warrior: {
set: 'spring2026FrogWarriorSet',
},
spring2026Rogue: {
set: 'spring2026BranchRogueSet',
},
spring2026Healer: {
set: 'spring2026SnowdropHealerSet',
},
spring2026Mage: {
set: 'spring2026MaypoleMageSet',
},
};
const headStats = {
@@ -2727,6 +2751,16 @@ const shield = {
winter2026Rogue: {
set: 'winter2026SkiRogueSet',
},
spring2026Warrior: {
set: 'spring2026FrogWarriorSet',
},
spring2026Rogue: {
set: 'spring2026BranchRogueSet',
notes: t('shieldSpecialSpring2026RogueNotes', { str: 8 }),
},
spring2026Healer: {
set: 'spring2026SnowdropHealerSet',
},
};
const shieldStats = {
@@ -3466,6 +3500,18 @@ const weapon = {
winter2026Rogue: {
set: 'winter2026SkiRogueSet',
},
spring2026Warrior: {
set: 'spring2026FrogWarriorSet',
},
spring2026Rogue: {
set: 'spring2026BranchRogueSet',
},
spring2026Healer: {
set: 'spring2026SnowdropHealerSet',
},
spring2026Mage: {
set: 'spring2026MaypoleMageSet',
},
};
const weaponStats = {

View File

@@ -0,0 +1,39 @@
import {
disableCache,
} from '../../middlewares/cache';
import SERVER_STATUS from '../../libs/serverStatus';
const api = {};
/**
* @api {get} /api/v3/ready Get Habitica's Server readiness status
* @apiName GetReady
* @apiGroup Status
*
* @apiSuccess {String} data.status 'ready' if everything is ok
*
* @apiSuccessExample {JSON} Server is Ready
* {
* 'status': 'ready',
* }
*/
api.getReady = {
method: 'GET',
url: '/ready',
// explicitly disable caching so that the server is always checked
middlewares: [disableCache],
async handler (req, res) {
// This allows kubernetes to determine if the server is ready to receive traffic
if (!SERVER_STATUS.MONGODB || !SERVER_STATUS.REDIS || !SERVER_STATUS.EXPRESS) {
res.respond(503, {
status: 'not ready',
});
} else {
res.respond(200, {
status: 'ready',
});
}
},
};
export default api;

View File

@@ -6,18 +6,10 @@ import moment from 'moment';
import md from 'habitica-markdown';
import csvStringify from '../../libs/csvStringify';
import { marshallUserData } from '../../libs/xmlMarshaller';
import { NotFound } from '../../libs/errors';
import * as Tasks from '../../models/task';
import * as inboxLib from '../../libs/inbox';
// import { model as User } from '../../models/user';
import { authWithSession } from '../../middlewares/auth';
/* import {
S3,
} from '../../libs/aws'; */
// const S3_BUCKET = nconf.get('S3_BUCKET');
// const BASE_URL = nconf.get('BASE_URL');
const api = {};
@@ -157,122 +149,6 @@ api.exportUserDataXml = {
},
};
/**
* @api {get} /export/avatar-:uuid.html Render a user avatar as an HTML page
* @apiName ExportUserAvatarHtml
* @apiDescription This HTML export feature is not currently working (https://github.com/HabitRPG/habitica/issues/9489).
* @apiGroup DataExport
*
* @apiParam (Path) {String} uuid The User ID of the user
*
* @apiSuccess {HTML} File An html page rendering the user's avatar.
*
* @apiUse UserNotFound
*/
// @TODO fix
api.exportUserAvatarHtml = {
method: 'GET',
url: '/export/avatar-:memberId.html',
// middlewares: [locals],
async handler (/* req, res */) {
throw new NotFound('This API route is currently not available. See https://github.com/HabitRPG/habitica/issues/9489.');
/* req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
const { memberId } = req.params;
throw new NotFound('This API route is currently not available. See https://github.com/HabitRPG/habitica/issues/9489.');
const member = await User
.findById(memberId)
.select('stats profile items achievements preferences backer contributor')
.exec();
if (!member) throw new NotFound(res.t('userWithIDNotFound', { userId: memberId }));
res.render('avatar-static', {
title: member.profile.name,
env: _.defaults({ user: member }, res.locals.habitrpg),
}); */
},
};
/**
* @api {get} /export/avatar-:uuid.png Render a user avatar as a PNG file
* @apiName ExportUserAvatarPng
* @apiDescription This PNG export feature is not currently working (https://github.com/HabitRPG/habitica/issues/9489).
* @apiGroup DataExport
*
* @apiParam (Path) {String} uuid The User ID of the user
*
* @apiSuccess {PNG} File A png file of the user's avatar.
*/
api.exportUserAvatarPng = {
method: 'GET',
url: '/export/avatar-:memberId.png',
async handler (/* req, res */) {
throw new NotFound('This API route is currently not available. See https://github.com/HabitRPG/habitica/issues/9489.');
/* req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
const { memberId } = req.params;
const filename = `avatars/${memberId}.png`;
const s3url = `https://${S3_BUCKET}.s3.amazonaws.com/${filename}`;
let response;
try {
response = await got.head(s3url); // TODO add timeout and retries
} catch (gotError) {
// If the file does not exist AWS S3 can return a 403 error
if (gotError.code !== 'ENOTFOUND' && gotError.statusCode
!== 404 && gotError.statusCode !== 403) {
throw gotError;
}
}
// cache images for 30 minutes on aws, else upload a new one
if (response && response.statusCode === 200 && moment()
.diff(response.headers['last-modified'], 'minutes') < 30) {
return res.redirect(s3url);
}
const pageBuffer = await new Pageres()
.src(`${BASE_URL}/export/avatar-${memberId}.html`, ['140x147'], {
crop: true,
filename: filename.replace('.png', ''),
})
.run();
const s3upload = S3.upload({
Bucket: S3_BUCKET,
Key: filename,
ACL: 'public-read',
StorageClass: 'REDUCED_REDUNDANCY',
ContentType: 'image/png',
Expires: moment().add({ minutes: 5 }).toDate(),
Body: pageBuffer,
});
const s3res = await new Promise((resolve, reject) => {
s3upload.send((err, s3uploadRes) => {
if (err) {
reject(err);
} else {
resolve(s3uploadRes);
}
});
});
return res.redirect(s3res.Location); */
},
};
/**
* @api {get} /export/inbox.html Export user private messages as HTML document
* @apiName ExportUserPrivateMessages

View File

@@ -3,6 +3,7 @@ import winston from 'winston';
import { Loggly } from 'winston-loggly-bulk';
import nconf from 'nconf';
import _ from 'lodash';
import os from 'os';
import {
CustomError,
} from './errors';
@@ -65,9 +66,8 @@ if (IS_PROD) {
),
}));
}
if (LOGGLY_TOKEN && LOGGLY_SUBDOMAIN) {
const tags = ['Winston-NodeJS'];
const tags = ['Winston-NodeJS', os.hostname()];
if (nconf.get('SERVER_EMOJI')) {
tags.push(nconf.get('SERVER_EMOJI'));
}

View File

@@ -5,6 +5,7 @@ import {
getDevelopmentConnectionUrl,
getDefaultConnectionOptions,
} from './mongodb';
import SERVER_STATUS from './serverStatus';
const IS_PROD = nconf.get('IS_PROD');
const MAINTENANCE_MODE = nconf.get('MAINTENANCE_MODE');
@@ -24,6 +25,13 @@ const connectionUrl = IS_PROD ? DB_URI : getDevelopmentConnectionUrl(DB_URI);
export default async function connectToMongoDB () {
// Do not connect to MongoDB when in maintenance mode
if (MAINTENANCE_MODE !== 'true') {
mongoose.connection.on('open', () => {
SERVER_STATUS.MONGODB = true;
});
mongoose.connection.on('disconnected', () => {
SERVER_STATUS.MONGODB = false;
});
return mongoose.connect(connectionUrl, mongooseOptions).then(() => {
logger.info('Connected with Mongoose.');
});

View File

@@ -0,0 +1,7 @@
const SERVER_STATUS = {
MONGODB: false,
REDIS: false,
EXPRESS: false,
};
export default SERVER_STATUS;

View File

@@ -10,6 +10,7 @@ import {
} from '../libs/errors';
import logger from '../libs/logger';
import { apiError } from '../libs/apiError';
import SERVER_STATUS from '../libs/serverStatus';
// Middleware to rate limit requests to the API
@@ -47,6 +48,14 @@ if (RATE_LIMITER_ENABLED) {
enable_offline_queue: false,
});
redisClient.on('ready', () => {
SERVER_STATUS.REDIS = true;
});
redisClient.on('reconnecting', () => {
SERVER_STATUS.REDIS = false;
});
redisClient.on('error', error => {
logger.error(error, 'Redis Error');
});
@@ -56,6 +65,8 @@ if (RATE_LIMITER_ENABLED) {
storeClient: redisClient,
});
}
} else {
SERVER_STATUS.REDIS = true;
}
function setResponseHeaders (res, rateLimiterRes) {

View File

@@ -41,7 +41,7 @@ export const logRequestData = (req, res, next) => {
export const logSlowRequests = (req, res, next) => {
req.requestStartTime = Date.now();
req.on('close', () => {
req.once('close', () => {
const requestTime = Date.now() - req.requestStartTime;
if (requestTime > SLOW_REQUEST_THRESHOLD) {
const data = buildBaseLogData(req);

View File

@@ -1,6 +1,8 @@
import nconf from 'nconf';
import express from 'express';
import http from 'http';
import mongoose from 'mongoose';
import redis from 'redis';
import logger from './libs/logger';
// Setup translations
@@ -18,12 +20,22 @@ import './libs/setupFirebase';
import './models/challenge';
import './models/group';
import './models/user';
import SERVER_STATUS from './libs/serverStatus';
connectToMongoDB();
const server = http.createServer();
const app = express();
process.on('SIGTERM', async () => {
console.log('SIGTERM signal received: closing HTTP server');
server.close(async () => {
await mongoose.disconnect();
await redis.quit();
process.exit(0);
});
});
app.set('port', nconf.get('PORT'));
attachMiddlewares(app, server);
@@ -31,6 +43,7 @@ attachMiddlewares(app, server);
server.on('request', app);
server.listen(app.get('port'), () => {
logger.info(`Express server listening on port ${app.get('port')}`);
SERVER_STATUS.EXPRESS = true;
});
export default server;