Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d8ab69b3c7 | |||
| bea77e9520 | |||
| 108201a465 | |||
| 32e51bd551 | |||
| 6e03e41271 | |||
| 8d9851a489 | |||
| ad60946eeb | |||
| c5e02292c4 | |||
| e2d0ddfb39 | |||
| 3db2cd49a4 | |||
| 02cac78896 | |||
| fef9c74f9b | |||
| 858a8749a4 | |||
| fd7c5b3847 | |||
| 73aa32ca31 | |||
| ead0b6c56f | |||
| e550ca1531 | |||
| 88059f568c | |||
| 9f85d3927f | |||
| d8badb6d12 | |||
| f5e4e2150a | |||
| 6743dcb08a | |||
| e7c8833c9a | |||
| 4de5140cf7 | |||
| 7de5a51247 | |||
| f1173cee6a | |||
| 0261d12bd9 | |||
| e3bcc48481 | |||
| 8bbb0ddaee | |||
| 7a0733f5ac | |||
| 474bc6a2b6 | |||
| 0af5593611 | |||
| 1b190a594a | |||
| e54bd8f242 | |||
| 0cc7c4a078 | |||
| 254a5cf423 | |||
| 18bc8c3d63 | |||
| 15753de3a1 | |||
| c93bf3e498 | |||
| e89ff95a21 | |||
| a02c4c1cfd | |||
| 616a0b7509 | |||
| 2a0bc030d8 | |||
| 42b00ed381 | |||
| 928c88f2da | |||
| 768e71228c | |||
| 6082f77977 | |||
| f6717a0bc1 | |||
| 9b3f8981e5 | |||
| 2758414b54 | |||
| e49aabdddf | |||
| aa371de8ae | |||
| 0acc7d19c5 | |||
| d861236f44 | |||
| 195928e471 | |||
| 566dd2b6b1 | |||
| 1a769d4a45 | |||
| f3fe1d76ad | |||
| 955c41f744 | |||
| 00c9fabf8b | |||
| 82d1df67c9 | |||
| 9449e6a883 | |||
| fa72684c53 | |||
| 45a22470fa | |||
| a388abc124 | |||
| 453d60b5bf | |||
| af1d13d3a2 | |||
| 680e86d2c9 | |||
| cc5e3ed123 | |||
| 4a6c0168a9 | |||
| 9d29f065e9 |
@@ -22,6 +22,7 @@ jobs:
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run lint-no-fix
|
||||
apidoc:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -42,6 +43,7 @@ jobs:
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run apidoc
|
||||
sanity:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -62,6 +64,7 @@ jobs:
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:sanity
|
||||
|
||||
common:
|
||||
@@ -83,6 +86,7 @@ jobs:
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:common
|
||||
content:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -103,6 +107,7 @@ jobs:
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:content
|
||||
|
||||
api-unit:
|
||||
@@ -110,6 +115,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
mongodb-version: [4.2]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
@@ -118,13 +124,18 @@ jobs:
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: sudo docker run --name mongo -d -p 27017:27017 mongo:4.2
|
||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||
uses: supercharge/mongodb-github-action@1.3.0
|
||||
with:
|
||||
mongodb-version: ${{ matrix.mongodb-version }}
|
||||
mongodb-replica-set: rs
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:api:unit
|
||||
env:
|
||||
REQUIRES_SERVER=true: true
|
||||
@@ -133,6 +144,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
mongodb-version: [4.2]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
@@ -141,13 +153,18 @@ jobs:
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: sudo docker run --name mongo -d -p 27017:27017 mongo:4.2
|
||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||
uses: supercharge/mongodb-github-action@1.3.0
|
||||
with:
|
||||
mongodb-version: ${{ matrix.mongodb-version }}
|
||||
mongodb-replica-set: rs
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:api-v3:integration
|
||||
env:
|
||||
REQUIRES_SERVER=true: true
|
||||
@@ -156,6 +173,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
mongodb-version: [4.2]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
@@ -164,13 +182,18 @@ jobs:
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: sudo docker run --name mongo -d -p 27017:27017 mongo:4.2
|
||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||
uses: supercharge/mongodb-github-action@1.3.0
|
||||
with:
|
||||
mongodb-version: ${{ matrix.mongodb-version }}
|
||||
mongodb-replica-set: rs
|
||||
- run: cp config.json.example config.json
|
||||
- name: npm install
|
||||
run: |
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:api-v4:integration
|
||||
env:
|
||||
REQUIRES_SERVER=true: true
|
||||
@@ -194,5 +217,6 @@ jobs:
|
||||
npm ci
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: test
|
||||
- run: npm run test:unit
|
||||
working-directory: ./website/client
|
||||
@@ -42,3 +42,7 @@ yarn.lock
|
||||
|
||||
# webstorm fake webpack for path intellisense
|
||||
webpack.webstorm.config
|
||||
|
||||
# mongodb replica set for local dev
|
||||
mongodb-*.tgz
|
||||
/mongodb-data
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"LOGGLY_SUBDOMAIN": "example-subdomain",
|
||||
"LOGGLY_TOKEN": "example-token",
|
||||
"MAINTENANCE_MODE": "false",
|
||||
"NODE_DB_URI": "mongodb://localhost:27017/habitrpg",
|
||||
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
|
||||
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
|
||||
"MONGODB_POOL_SIZE": "10",
|
||||
"NODE_ENV": "development",
|
||||
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
|
||||
@@ -70,7 +71,6 @@
|
||||
"SLACK_URL": "https://hooks.slack.com/services/some-url",
|
||||
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
|
||||
"TEST_DB_URI": "mongodb://localhost:27017/habitrpg_test",
|
||||
"TRANSIFEX_SLACK_CHANNEL": "transifex",
|
||||
"WEB_CONCURRENCY": 1,
|
||||
"SKIP_SSL_CHECK_KEY": "key",
|
||||
@@ -80,5 +80,9 @@
|
||||
"APPLE_AUTH_CLIENT_ID": "",
|
||||
"APPLE_AUTH_KEY_ID": "",
|
||||
"BLOCKED_IPS": "",
|
||||
"LOG_AMPLITUDE_EVENTS": "false"
|
||||
"LOG_AMPLITUDE_EVENTS": "false",
|
||||
"RATE_LIMITER_ENABLED": "false",
|
||||
"REDIS_HOST": "aaabbbcccdddeeefff",
|
||||
"REDIS_PORT": "1234",
|
||||
"REDIS_PASSWORD": "12345678"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import gulp from 'gulp';
|
||||
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 clean from 'rimraf';
|
||||
|
||||
gulp.task('build:babel:server', () => gulp.src('website/server/**/*.js')
|
||||
.pipe(babel())
|
||||
@@ -24,10 +29,67 @@ gulp.task('build:prod', gulp.series(
|
||||
done => done(),
|
||||
));
|
||||
|
||||
// Due to this issue https://github.com/vkarpov15/run-rs/issues/45
|
||||
// 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/');
|
||||
|
||||
gulp.task('build:prepare-mongo', async () => {
|
||||
if (fs.existsSync(MONGO_PATH)) {
|
||||
// console.log('MongoDB data folder exists, skipping setup.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (os.platform() !== 'win32') {
|
||||
// console.log('Not on Windows, skipping MongoDB setup.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('MongoDB data folder is missing, setting up.');
|
||||
|
||||
// use run-rs without --keep, kill it as soon as the replica set starts
|
||||
const runRsProcess = spawn('run-rs', ['-v', '4.2.8', '-l', 'ubuntu1804', '--dbpath', 'mongodb-data', '--number', '1', '--quiet']);
|
||||
|
||||
for await (const chunk of runRsProcess.stdout) {
|
||||
const stringChunk = chunk.toString();
|
||||
console.log(stringChunk);
|
||||
// kills the process after the replica set is setup
|
||||
if (stringChunk.includes('Started replica set')) {
|
||||
console.log('MongoDB setup correctly.');
|
||||
runRsProcess.kill();
|
||||
}
|
||||
}
|
||||
|
||||
let error = '';
|
||||
for await (const chunk of runRsProcess.stderr) {
|
||||
const stringChunk = chunk.toString();
|
||||
error += stringChunk;
|
||||
}
|
||||
|
||||
const exitCode = await new Promise(resolve => {
|
||||
runRsProcess.on('close', resolve);
|
||||
});
|
||||
|
||||
if (exitCode || error.length > 0) {
|
||||
// remove any leftover files
|
||||
clean.sync(MONGO_PATH);
|
||||
|
||||
throw new Error(`Error running run-rs: ${error}`);
|
||||
}
|
||||
});
|
||||
|
||||
gulp.task('build:dev', gulp.series(
|
||||
'build:prepare-mongo',
|
||||
done => done(),
|
||||
));
|
||||
|
||||
const buildArgs = [];
|
||||
|
||||
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
|
||||
buildArgs.push('build:prod');
|
||||
} else if (process.env.NODE_ENV !== 'test') { // eslint-disable-line no-process-env
|
||||
buildArgs.push('build:dev');
|
||||
}
|
||||
|
||||
gulp.task('build', gulp.series(buildArgs, done => {
|
||||
|
||||
@@ -3,6 +3,10 @@ import nconf from 'nconf';
|
||||
import repl from 'repl';
|
||||
import gulp from 'gulp';
|
||||
import logger from '../website/server/libs/logger';
|
||||
import {
|
||||
getDevelopmentConnectionUrl,
|
||||
getDefaultConnectionOptions,
|
||||
} from '../website/server/libs/mongodb';
|
||||
|
||||
// Add additional properties to the repl's context
|
||||
const improveRepl = context => {
|
||||
@@ -26,13 +30,14 @@ const improveRepl = context => {
|
||||
context.Group = require('../website/server/models/group').model; // eslint-disable-line global-require
|
||||
context.User = require('../website/server/models/user').model; // eslint-disable-line global-require
|
||||
|
||||
const isProd = nconf.get('NODE_ENV') === 'production';
|
||||
const mongooseOptions = !isProd ? {} : {
|
||||
keepAlive: 1,
|
||||
connectTimeoutMS: 30000,
|
||||
};
|
||||
const IS_PROD = nconf.get('NODE_ENV') === 'production';
|
||||
const NODE_DB_URI = nconf.get('NODE_DB_URI');
|
||||
|
||||
const mongooseOptions = getDefaultConnectionOptions();
|
||||
const connectionUrl = IS_PROD ? NODE_DB_URI : getDevelopmentConnectionUrl(NODE_DB_URI);
|
||||
|
||||
mongoose.connect(
|
||||
nconf.get('NODE_DB_URI'),
|
||||
connectionUrl,
|
||||
mongooseOptions,
|
||||
err => {
|
||||
if (err) throw err;
|
||||
|
||||
@@ -6,6 +6,10 @@ import nconf from 'nconf';
|
||||
import {
|
||||
pipe,
|
||||
} from './taskHelper';
|
||||
import {
|
||||
getDevelopmentConnectionUrl,
|
||||
getDefaultConnectionOptions,
|
||||
} from '../website/server/libs/mongodb';
|
||||
|
||||
// TODO rewrite
|
||||
|
||||
@@ -44,7 +48,10 @@ gulp.task('test:nodemon', gulp.series(done => {
|
||||
}, 'nodemon'));
|
||||
|
||||
gulp.task('test:prepare:mongo', cb => {
|
||||
mongoose.connect(TEST_DB_URI, err => {
|
||||
const mongooseOptions = getDefaultConnectionOptions();
|
||||
const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI);
|
||||
|
||||
mongoose.connect(connectionUrl, mongooseOptions, err => {
|
||||
if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`);
|
||||
return mongoose.connection.dropDatabase(err2 => {
|
||||
if (err2) return cb(err2);
|
||||
@@ -176,7 +183,7 @@ gulp.task('test:api:unit:run', done => {
|
||||
|
||||
gulp.task('test:api:unit:watch', () => gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit:run', done => done())));
|
||||
|
||||
gulp.task('test:api-v3:integration', done => {
|
||||
gulp.task('test:api-v3:integration', gulp.series('test:prepare:mongo', done => {
|
||||
const runner = exec(
|
||||
testBin('istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
|
||||
{ maxBuffer: 500 * 1024 },
|
||||
@@ -189,7 +196,7 @@ gulp.task('test:api-v3:integration', done => {
|
||||
);
|
||||
|
||||
pipe(runner);
|
||||
});
|
||||
}));
|
||||
|
||||
gulp.task('test:api-v3:integration:watch', () => gulp.watch([
|
||||
'website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js',
|
||||
@@ -206,7 +213,7 @@ gulp.task('test:api-v3:integration:separate-server', done => {
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:api-v4:integration', done => {
|
||||
gulp.task('test:api-v4:integration', gulp.series('test:prepare:mongo', done => {
|
||||
const runner = exec(
|
||||
testBin('istanbul cover --dir coverage/api-v4-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v4 --recursive --require ./test/helpers/start-server'),
|
||||
{ maxBuffer: 500 * 1024 },
|
||||
@@ -219,7 +226,7 @@ gulp.task('test:api-v4:integration', done => {
|
||||
);
|
||||
|
||||
pipe(runner);
|
||||
});
|
||||
}));
|
||||
|
||||
gulp.task('test:api-v4:integration:separate-server', done => {
|
||||
const runner = exec(
|
||||
@@ -231,11 +238,16 @@ gulp.task('test:api-v4:integration:separate-server', done => {
|
||||
pipe(runner);
|
||||
});
|
||||
|
||||
gulp.task('test:api:unit', gulp.series(
|
||||
'test:prepare:mongo',
|
||||
'test:api:unit:run',
|
||||
done => done(),
|
||||
));
|
||||
|
||||
gulp.task('test', gulp.series(
|
||||
'test:sanity',
|
||||
'test:content',
|
||||
'test:common',
|
||||
'test:prepare:mongo',
|
||||
'test:api:unit:run',
|
||||
'test:api-v3:integration',
|
||||
'test:api-v4:integration',
|
||||
@@ -243,14 +255,7 @@ gulp.task('test', gulp.series(
|
||||
));
|
||||
|
||||
gulp.task('test:api-v3', gulp.series(
|
||||
'test:prepare:mongo',
|
||||
'test:api:unit:run',
|
||||
'test:api:unit',
|
||||
'test:api-v3:integration',
|
||||
done => done(),
|
||||
));
|
||||
|
||||
gulp.task('test:api:unit', gulp.series(
|
||||
'test:prepare:mongo',
|
||||
'test:api:unit:run',
|
||||
done => done(),
|
||||
));
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20200721_summer_splash_orcas';
|
||||
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
let set;
|
||||
|
||||
if (user && user.items && user.items.pets && typeof user.items.pets['Orca-Base'] !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME };
|
||||
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Orca-Base'] !== 'undefined') {
|
||||
set = { migration: MIGRATION_NAME, 'items.pets.Orca-Base': 5 };
|
||||
} else {
|
||||
set = { migration: MIGRATION_NAME, 'items.mounts.Orca-Base': true };
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({ _id: user._id }, { $set: set }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2020-06-21')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -18,7 +18,7 @@ function setUpServer () {
|
||||
setUpServer();
|
||||
|
||||
// Replace this with your migration
|
||||
const processUsers = require().default;
|
||||
const processUsers = require('./archive/2020/20200721_summer_splash_orcas').default;
|
||||
|
||||
processUsers()
|
||||
.then(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.148.1",
|
||||
"version": "4.150.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.10.3",
|
||||
@@ -30,7 +30,7 @@
|
||||
"express-basic-auth": "^1.1.5",
|
||||
"express-validator": "^5.2.0",
|
||||
"glob": "^7.1.6",
|
||||
"got": "^11.3.0",
|
||||
"got": "^11.5.0",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
@@ -43,15 +43,15 @@
|
||||
"js2xmlparser": "^4.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwks-rsa": "^1.8.1",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash": "^4.17.19",
|
||||
"merge-stream": "^2.0.0",
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.27.0",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.9.20",
|
||||
"mongoose": "^5.9.23",
|
||||
"morgan": "^1.10.0",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^1.0.2",
|
||||
"node-gcm": "^1.0.3",
|
||||
"on-headers": "^1.0.2",
|
||||
"passport": "^0.4.1",
|
||||
"passport-facebook": "^3.0.0",
|
||||
@@ -60,13 +60,15 @@
|
||||
"paypal-ipn": "3.0.0",
|
||||
"paypal-rest-sdk": "^1.8.1",
|
||||
"ps-tree": "^1.0.0",
|
||||
"rate-limiter-flexible": "^2.1.7",
|
||||
"redis": "^3.0.2",
|
||||
"regenerator-runtime": "^0.13.5",
|
||||
"remove-markdown": "^0.3.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"short-uuid": "^3.0.0",
|
||||
"stripe": "^7.15.0",
|
||||
"superagent": "^5.3.1",
|
||||
"universal-analytics": "^0.4.17",
|
||||
"universal-analytics": "^0.4.23",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^8.2.0",
|
||||
"validator": "^13.1.1",
|
||||
@@ -102,6 +104,7 @@
|
||||
"client:unit": "cd website/client && npm run test:unit",
|
||||
"start": "gulp nodemon",
|
||||
"debug": "gulp nodemon --inspect",
|
||||
"mongo:dev": "run-rs -v 4.2.8 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
|
||||
"postinstall": "gulp build && cd website/client && npm install",
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
@@ -110,11 +113,13 @@
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chalk": "^4.1.0",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"expect.js": "^0.3.1",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"mocha": "^5.1.1",
|
||||
"monk": "^7.3.0",
|
||||
"require-again": "^2.0.0",
|
||||
"run-rs": "^0.6.2",
|
||||
"sinon": "^9.0.2",
|
||||
"sinon-chai": "^3.5.0",
|
||||
"sinon-stub-promise": "^4.0.0"
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import os from 'os';
|
||||
import nconf from 'nconf';
|
||||
import requireAgain from 'require-again';
|
||||
|
||||
const pathToMongoLib = '../../../../website/server/libs/mongodb';
|
||||
|
||||
describe('mongodb', () => {
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('getDevelopmentConnectionUrl', () => {
|
||||
it('returns the original connection url if not on windows', () => {
|
||||
sandbox.stub(os, 'platform').returns('linux');
|
||||
const mongoLibOverride = requireAgain(pathToMongoLib);
|
||||
|
||||
const originalString = 'mongodb://localhost:3030';
|
||||
const string = mongoLibOverride.getDevelopmentConnectionUrl(originalString);
|
||||
expect(string).to.equal(originalString);
|
||||
});
|
||||
|
||||
it('replaces localhost with hostname on windows', () => {
|
||||
sandbox.stub(os, 'platform').returns('win32');
|
||||
sandbox.stub(os, 'hostname').returns('hostname');
|
||||
const mongoLibOverride = requireAgain(pathToMongoLib);
|
||||
|
||||
const originalString = 'mongodb://localhost:3030';
|
||||
const string = mongoLibOverride.getDevelopmentConnectionUrl(originalString);
|
||||
expect(string).to.equal('mongodb://hostname:3030');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDefaultConnectionOptions', () => {
|
||||
it('returns development config when IS_PROD is false', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false);
|
||||
const mongoLibOverride = requireAgain(pathToMongoLib);
|
||||
|
||||
const options = mongoLibOverride.getDefaultConnectionOptions();
|
||||
expect(options).to.have.all.keys(['useNewUrlParser', 'useUnifiedTopology']);
|
||||
});
|
||||
|
||||
it('returns production config when IS_PROD is true', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||
const mongoLibOverride = requireAgain(pathToMongoLib);
|
||||
|
||||
const options = mongoLibOverride.getDefaultConnectionOptions();
|
||||
expect(options).to.have.all.keys(['useNewUrlParser', 'useUnifiedTopology', 'keepAlive', 'keepAliveInitialDelay']);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -21,7 +21,8 @@ describe('cors middleware', () => {
|
||||
expect(res.set).to.have.been.calledWith({
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE',
|
||||
'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client',
|
||||
'Access-Control-Allow-Headers': 'Authorization,Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client',
|
||||
'Access-Control-Expose-Headers': 'X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,Retry-After',
|
||||
});
|
||||
expect(res.sendStatus).to.not.have.been.called;
|
||||
expect(next).to.have.been.calledOnce;
|
||||
@@ -33,7 +34,8 @@ describe('cors middleware', () => {
|
||||
expect(res.set).to.have.been.calledWith({
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE',
|
||||
'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client',
|
||||
'Access-Control-Allow-Headers': 'Authorization,Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client',
|
||||
'Access-Control-Expose-Headers': 'X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,Retry-After',
|
||||
});
|
||||
expect(res.sendStatus).to.have.been.calledWith(200);
|
||||
expect(next).to.not.have.been.called;
|
||||
|
||||
@@ -57,7 +57,7 @@ describe('ipBlocker middleware', () => {
|
||||
});
|
||||
|
||||
it('does not throw when the ip does not match', () => {
|
||||
req.headers['x-forwarded-for'] = '192.168.1.1';
|
||||
req.ip = '192.168.1.1';
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.2');
|
||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
||||
attachIpBlocker(req, res, next);
|
||||
@@ -65,30 +65,12 @@ describe('ipBlocker middleware', () => {
|
||||
checkErrorNotThrown(next);
|
||||
});
|
||||
|
||||
it('throws when a matching ip exist in x-forwarded-for', () => {
|
||||
req.headers['x-forwarded-for'] = '192.168.1.1';
|
||||
it('throws when the ip is blocked', () => {
|
||||
req.ip = '192.168.1.1';
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1');
|
||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
||||
attachIpBlocker(req, res, next);
|
||||
|
||||
checkErrorThrown(next);
|
||||
});
|
||||
|
||||
it('trims ips in x-forwarded-for', () => {
|
||||
req.headers['x-forwarded-for'] = '192.168.1.1';
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(', 192.168.1.1 , 192.168.1.4, ');
|
||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
||||
attachIpBlocker(req, res, next);
|
||||
|
||||
checkErrorThrown(next);
|
||||
});
|
||||
|
||||
it('works when multiple ips are passed in x-forwarded-for', () => {
|
||||
req.headers['x-forwarded-for'] = '192.168.1.4';
|
||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1, 192.168.1.4, 192.168.1.3');
|
||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
||||
attachIpBlocker(req, res, next);
|
||||
|
||||
checkErrorThrown(next);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import nconf from 'nconf';
|
||||
import { RateLimiterMemory } from 'rate-limiter-flexible';
|
||||
import requireAgain from 'require-again';
|
||||
import {
|
||||
generateRes,
|
||||
generateReq,
|
||||
generateNext,
|
||||
} from '../../../helpers/api-unit.helper';
|
||||
import { TooManyRequests } from '../../../../website/server/libs/errors';
|
||||
import apiError from '../../../../website/server/libs/apiError';
|
||||
import logger from '../../../../website/server/libs/logger';
|
||||
|
||||
describe('rateLimiter middleware', () => {
|
||||
const pathToRateLimiter = '../../../../website/server/middlewares/rateLimiter';
|
||||
|
||||
let res; let req; let next; let nconfGetStub;
|
||||
|
||||
beforeEach(() => {
|
||||
nconfGetStub = sandbox.stub(nconf, 'get');
|
||||
|
||||
nconfGetStub.withArgs('NODE_ENV').returns('test');
|
||||
nconfGetStub.withArgs('IS_TEST').returns(true);
|
||||
|
||||
res = generateRes();
|
||||
req = generateReq();
|
||||
next = generateNext();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('is disabled when the env var is not defined', () => {
|
||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns(undefined);
|
||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||
attachRateLimiter(req, res, next);
|
||||
|
||||
expect(next).to.have.been.calledOnce;
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(typeof calledWith[0] === 'undefined').to.equal(true);
|
||||
expect(res.set).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('is disabled when the env var is an not "true"', () => {
|
||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('false');
|
||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||
attachRateLimiter(req, res, next);
|
||||
|
||||
expect(next).to.have.been.calledOnce;
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(typeof calledWith[0] === 'undefined').to.equal(true);
|
||||
expect(res.set).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('does not throw when there are available points', async () => {
|
||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||
await attachRateLimiter(req, res, next);
|
||||
|
||||
expect(next).to.have.been.calledOnce;
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(typeof calledWith[0] === 'undefined').to.equal(true);
|
||||
|
||||
expect(res.set).to.have.been.calledOnce;
|
||||
expect(res.set).to.have.been.calledWithMatch({
|
||||
'X-RateLimit-Limit': 30,
|
||||
'X-RateLimit-Remaining': 29,
|
||||
'X-RateLimit-Reset': sinon.match(Date),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not throw when an unknown error is thrown by the rate limiter', async () => {
|
||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||
sandbox.stub(logger, 'error');
|
||||
sandbox.stub(RateLimiterMemory.prototype, 'consume')
|
||||
.returns(Promise.reject(new Error('Unknown error.')));
|
||||
|
||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||
await attachRateLimiter(req, res, next);
|
||||
|
||||
expect(next).to.have.been.calledOnce;
|
||||
const calledWith = next.getCall(0).args;
|
||||
expect(typeof calledWith[0] === 'undefined').to.equal(true);
|
||||
expect(res.set).to.not.have.been.called;
|
||||
|
||||
expect(logger.error).to.be.calledOnce;
|
||||
expect(logger.error).to.have.been.calledWithMatch(Error, 'Rate Limiter Error');
|
||||
});
|
||||
|
||||
it('throws when there are no available points remaining', async () => {
|
||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||
|
||||
// call for 31 times
|
||||
for (let i = 0; i < 31; i += 1) {
|
||||
await attachRateLimiter(req, res, next); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
|
||||
expect(next).to.have.been.callCount(31);
|
||||
const calledWith = next.getCall(30).args;
|
||||
expect(calledWith[0].message).to.equal(apiError('clientRateLimited'));
|
||||
expect(calledWith[0] instanceof TooManyRequests).to.equal(true);
|
||||
|
||||
expect(res.set).to.have.been.callCount(31);
|
||||
expect(res.set).to.have.been.calledWithMatch({
|
||||
'Retry-After': sinon.match(Number),
|
||||
'X-RateLimit-Limit': 30,
|
||||
'X-RateLimit-Remaining': 0,
|
||||
'X-RateLimit-Reset': sinon.match(Date),
|
||||
});
|
||||
});
|
||||
|
||||
it('uses the user id if supplied or the ip address', async () => {
|
||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||
|
||||
req.ip = 1;
|
||||
await attachRateLimiter(req, res, next);
|
||||
|
||||
req.headers['x-api-user'] = 'user-1';
|
||||
await attachRateLimiter(req, res, next);
|
||||
await attachRateLimiter(req, res, next);
|
||||
|
||||
// user id an ip are counted as separate sources
|
||||
expect(res.set).to.have.been.calledWithMatch({
|
||||
'X-RateLimit-Limit': 30,
|
||||
'X-RateLimit-Remaining': 28, // 2 calls with user id
|
||||
'X-RateLimit-Reset': sinon.match(Date),
|
||||
});
|
||||
|
||||
req.headers['x-api-user'] = undefined;
|
||||
await attachRateLimiter(req, res, next);
|
||||
await attachRateLimiter(req, res, next);
|
||||
|
||||
expect(res.set).to.have.been.calledWithMatch({
|
||||
'X-RateLimit-Limit': 30,
|
||||
'X-RateLimit-Remaining': 27, // 3 calls with only ip
|
||||
'X-RateLimit-Reset': sinon.match(Date),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -22,7 +22,7 @@ describe('redirects middleware', () => {
|
||||
const nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.protocol = 'http';
|
||||
req.originalUrl = '/static/front';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
@@ -37,7 +37,7 @@ describe('redirects middleware', () => {
|
||||
const nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('https');
|
||||
req.protocol = 'https';
|
||||
req.originalUrl = '/static/front';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
@@ -51,7 +51,7 @@ describe('redirects middleware', () => {
|
||||
const nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||
nconfStub.withArgs('IS_PROD').returns(false);
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.protocol = 'http';
|
||||
req.originalUrl = '/static/front';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
@@ -65,7 +65,7 @@ describe('redirects middleware', () => {
|
||||
const nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('http://habitica.com');
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.protocol = 'http';
|
||||
req.originalUrl = '/static/front';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
@@ -81,7 +81,7 @@ describe('redirects middleware', () => {
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key');
|
||||
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.protocol = 'http';
|
||||
req.originalUrl = '/static/front';
|
||||
req.query.skipSSLCheck = 'test-key';
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('redirects middleware', () => {
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key');
|
||||
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.protocol = 'http';
|
||||
req.originalUrl = '/static/front?skipSSLCheck=INVALID';
|
||||
req.query.skipSSLCheck = 'INVALID';
|
||||
|
||||
@@ -114,7 +114,7 @@ describe('redirects middleware', () => {
|
||||
nconfStub.withArgs('IS_PROD').returns(true);
|
||||
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns(null);
|
||||
|
||||
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
|
||||
req.protocol = 'http';
|
||||
req.originalUrl = '/static/front';
|
||||
req.query.skipSSLCheck = 'INVALID';
|
||||
|
||||
|
||||
@@ -35,6 +35,33 @@ describe('Challenge Model', () => {
|
||||
notes: 'test notes',
|
||||
},
|
||||
};
|
||||
const tasks2ToTest = {
|
||||
habit: {
|
||||
text: 'test habit 2',
|
||||
type: 'habit',
|
||||
up: false,
|
||||
down: true,
|
||||
notes: 'test notes',
|
||||
},
|
||||
todo: {
|
||||
text: 'test todo 2',
|
||||
type: 'todo',
|
||||
notes: 'test notes',
|
||||
},
|
||||
daily: {
|
||||
text: 'test daily 2',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
notes: 'test notes',
|
||||
},
|
||||
reward: {
|
||||
text: 'test reward 2',
|
||||
type: 'reward',
|
||||
notes: 'test notes',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
guild = new Group({
|
||||
@@ -146,6 +173,60 @@ describe('Challenge Model', () => {
|
||||
expect(syncedTask.attribute).to.eql('str');
|
||||
});
|
||||
|
||||
it('should add challenge tag back to user upon syncing challenge tasks to a user with challenge tag removed', async () => {
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
const newMember = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
await newMember.save();
|
||||
await challenge.syncTasksToUser(newMember);
|
||||
|
||||
let updatedNewMember = await User.findById(newMember._id).exec();
|
||||
const updatedNewMemberId = updatedNewMember._id;
|
||||
|
||||
updatedNewMember.tags = [];
|
||||
await updatedNewMember.save();
|
||||
|
||||
const taskValue2 = tasks2ToTest[taskType];
|
||||
const task2 = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue2));
|
||||
task2.challenge.id = challenge._id;
|
||||
await challenge.addTasks([task2]);
|
||||
await challenge.syncTasksToUser(updatedNewMember);
|
||||
|
||||
updatedNewMember = await User.findById(updatedNewMemberId).exec();
|
||||
|
||||
expect(updatedNewMember.tags.length).to.equal(1);
|
||||
expect(updatedNewMember.tags[0].id).to.equal(challenge._id);
|
||||
expect(updatedNewMember.tags[0].name).to.equal(challenge.shortName);
|
||||
});
|
||||
|
||||
it('should not add a duplicate challenge tag to user upon syncing challenge tasks to a user with existing challenge tag', async () => {
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
const newMember = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
await newMember.save();
|
||||
await challenge.syncTasksToUser(newMember);
|
||||
|
||||
let updatedNewMember = await User.findById(newMember._id).exec();
|
||||
const updatedNewMemberId = updatedNewMember._id;
|
||||
|
||||
const taskValue2 = tasks2ToTest[taskType];
|
||||
const task2 = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue2));
|
||||
task2.challenge.id = challenge._id;
|
||||
await challenge.addTasks([task2]);
|
||||
await challenge.syncTasksToUser(updatedNewMember);
|
||||
|
||||
updatedNewMember = await User.findById(updatedNewMemberId);
|
||||
|
||||
expect(updatedNewMember.tags.length).to.equal(8);
|
||||
expect(updatedNewMember.tags[7].id).to.equal(challenge._id);
|
||||
expect(updatedNewMember.tags[7].name).to.equal(challenge.shortName);
|
||||
expect(updatedNewMember.tags.filter(tag => tag.id === challenge._id).length).to.equal(1);
|
||||
});
|
||||
|
||||
it('syncs challenge tasks to a user with the existing task', async () => {
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('returns only first 30 members if req.query.includeAllMembers is not true', async () => {
|
||||
it('returns only first 30 members if req.query.includeAllMembers is not true and req.query.limit is undefined', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
@@ -136,7 +136,7 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns only first 30 members if req.query.includeAllMembers is not defined', async () => {
|
||||
it('returns only first 30 members if req.query.includeAllMembers is not defined and req.query.limit is undefined', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
@@ -155,6 +155,68 @@ describe('GET /challenges/:challengeId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is over 60', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', privacy: 'private' });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
const anotherUser = await generateUser();
|
||||
|
||||
await expect(anotherUser.get(`/challenges/${challenge._id}/members?limit=61`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is under 1', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', privacy: 'private' });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
const anotherUser = await generateUser();
|
||||
|
||||
await expect(anotherUser.get(`/challenges/${challenge._id}/members?limit=-13`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is not an integer', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', privacy: 'private' });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
const anotherUser = await generateUser();
|
||||
|
||||
await expect(anotherUser.get(`/challenges/${challenge._id}/members?limit=true`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns up to 60 members when req.query.limit is specified', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
const usersToGenerate = [];
|
||||
for (let i = 0; i < 62; i += 1) {
|
||||
usersToGenerate.push(generateUser({ challenges: [challenge._id] }));
|
||||
}
|
||||
await Promise.all(usersToGenerate);
|
||||
|
||||
let res = await user.get(`/challenges/${challenge._id}/members?limit=57`);
|
||||
expect(res.length).to.equal(57);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
res = await user.get(`/challenges/${challenge._id}/members?limit=60&lastId=${res[res.length - 1]._id}`);
|
||||
expect(res.length).to.equal(6);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
}).timeout(30000);
|
||||
|
||||
it('returns all members if req.query.includeAllMembers is true', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
const challenge = await generateChallenge(user, group);
|
||||
|
||||
@@ -70,7 +70,7 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('returns only first 30 invites', async () => {
|
||||
it('returns only first 30 invites by default (req.query.limit not specified)', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
@@ -89,6 +89,65 @@ describe('GET /groups/:groupId/invites', () => {
|
||||
});
|
||||
}).timeout(10000);
|
||||
|
||||
it('returns an error if req.query.limit is over 60', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=61`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is under 1', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=-1`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is not an integer', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
await expect(leader.get(`/groups/${group._id}/invites?limit=1.3`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns up to 60 invites when req.query.limit is specified', async () => {
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
||||
|
||||
const invitesToGenerate = [];
|
||||
for (let i = 0; i < 31; i += 1) {
|
||||
invitesToGenerate.push(generateUser());
|
||||
}
|
||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||
await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) });
|
||||
|
||||
let res = await leader.get(`/groups/${group._id}/invites?limit=14`);
|
||||
expect(res.length).to.equal(14);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
res = await leader.get(`/groups/${group._id}/invites?limit=31`);
|
||||
expect(res.length).to.equal(31);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
}).timeout(30000);
|
||||
|
||||
it('supports using req.query.lastId to get more invites', async function test () {
|
||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||
const leader = await generateUser({ balance: 4 });
|
||||
|
||||
@@ -116,7 +116,7 @@ describe('GET /groups/:groupId/members', () => {
|
||||
expect(memberRes.inbox.messages).to.not.exist;
|
||||
});
|
||||
|
||||
it('returns only first 30 members', async () => {
|
||||
it('returns only first 30 members by default (req.query.limit not specified)', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
|
||||
const usersToGenerate = [];
|
||||
@@ -133,6 +133,60 @@ describe('GET /groups/:groupId/members', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is over 60', async () => {
|
||||
await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
|
||||
await expect(user.get('/groups/party/members?limit=61')).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is under 1', async () => {
|
||||
await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
|
||||
await expect(user.get('/groups/party/members?limit=0')).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if req.query.limit is not an integer', async () => {
|
||||
await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
|
||||
await expect(user.get('/groups/party/members?limit=1.1')).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns up to 60 members when req.query.limit is specified', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
|
||||
const usersToGenerate = [];
|
||||
for (let i = 0; i < 62; i += 1) {
|
||||
usersToGenerate.push(generateUser({ party: { _id: group._id } }));
|
||||
}
|
||||
await Promise.all(usersToGenerate);
|
||||
|
||||
let res = await user.get('/groups/party/members?limit=60');
|
||||
expect(res.length).to.equal(60);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
res = await user.get(`/groups/party/members?limit=60&lastId=${res[res.length - 1]._id}`);
|
||||
expect(res.length).to.equal(3);
|
||||
res.forEach(member => {
|
||||
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
|
||||
expect(member.profile).to.have.all.keys(['name']);
|
||||
});
|
||||
}).timeout(30000);
|
||||
|
||||
it('returns only first 30 members even when ?includeAllMembers=true', async () => {
|
||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||
|
||||
|
||||
@@ -203,6 +203,16 @@ describe('POST /group/:groupId/join', () => {
|
||||
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
||||
});
|
||||
|
||||
it('Issue #12291: accepting a redundant party invite will let the user stay in the party', async () => {
|
||||
await invitedUser.update({
|
||||
'party._id': party._id,
|
||||
});
|
||||
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
||||
await invitedUser.post(`/groups/${party._id}/join`);
|
||||
|
||||
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
||||
});
|
||||
|
||||
it('notifies inviting user that their invitation was accepted', async () => {
|
||||
await invitedUser.post(`/groups/${party._id}/join`);
|
||||
|
||||
|
||||
@@ -41,6 +41,29 @@ describe('POST /user/feed/:pet/:food', () => {
|
||||
expect(user.items.pets['Wolf-Base']).to.equal(7);
|
||||
});
|
||||
|
||||
it('bulk feeding pet with non-preferred food', async () => {
|
||||
await user.update({
|
||||
'items.pets.Wolf-Base': 5,
|
||||
'items.food.Milk': 3,
|
||||
});
|
||||
|
||||
const food = content.food.Milk;
|
||||
const pet = content.petInfo['Wolf-Base'];
|
||||
|
||||
const res = await user.post('/user/feed/Wolf-Base/Milk?amount=2');
|
||||
await user.sync();
|
||||
expect(res).to.eql({
|
||||
data: user.items.pets['Wolf-Base'],
|
||||
message: t('messageDontEnjoyFood', {
|
||||
egg: pet.text(),
|
||||
foodText: food.textThe(),
|
||||
}),
|
||||
});
|
||||
|
||||
expect(user.items.food.Milk).to.eql(1);
|
||||
expect(user.items.pets['Wolf-Base']).to.equal(9);
|
||||
});
|
||||
|
||||
context('sending user activity webhooks', () => {
|
||||
before(async () => {
|
||||
await server.start();
|
||||
@@ -77,5 +100,33 @@ describe('POST /user/feed/:pet/:food', () => {
|
||||
expect(body.pet).to.eql('Wolf-Base');
|
||||
expect(body.message).to.eql(res.message);
|
||||
});
|
||||
|
||||
it('sends user activity webhook (mount raised after full bulk feeding)', async () => {
|
||||
const uuid = generateUUID();
|
||||
|
||||
await user.post('/user/webhook', {
|
||||
url: `http://localhost:${server.port}/webhooks/${uuid}`,
|
||||
type: 'userActivity',
|
||||
enabled: true,
|
||||
options: {
|
||||
mountRaised: true,
|
||||
},
|
||||
});
|
||||
|
||||
await user.update({
|
||||
'items.pets.Wolf-Base': 47,
|
||||
'items.food.Milk': 3,
|
||||
});
|
||||
const res = await user.post('/user/feed/Wolf-Base/Milk?amount=2');
|
||||
|
||||
await sleep();
|
||||
|
||||
const body = server.getWebhookData(uuid);
|
||||
|
||||
expect(user.achievements.allYourBase).to.not.equal(true);
|
||||
expect(body.type).to.eql('mountRaised');
|
||||
expect(body.pet).to.eql('Wolf-Base');
|
||||
expect(body.message).to.eql(res.message);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,6 +92,14 @@ describe('PUT /user', () => {
|
||||
error: 'BadRequest',
|
||||
message: t('displaynameIssueSlur'),
|
||||
});
|
||||
|
||||
await expect(user.put('/user', {
|
||||
'profile.name': 'namecontainsnewline\n',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('displaynameIssueNewline'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -127,19 +127,26 @@ describe('PUT /user/webhook/:id', () => {
|
||||
it('can update taskActivity options', async () => {
|
||||
const type = 'taskActivity';
|
||||
const options = {
|
||||
checklistScored: true,
|
||||
updated: false,
|
||||
deleted: true,
|
||||
scored: false,
|
||||
};
|
||||
|
||||
const webhook = await user.put(`/user/webhook/${webhookToUpdate.id}`, { type, options });
|
||||
|
||||
expect(webhook.options).to.eql({
|
||||
checklistScored: false, // starting value
|
||||
const expected = {
|
||||
checklistScored: true,
|
||||
created: true, // starting value
|
||||
updated: false,
|
||||
deleted: true,
|
||||
scored: true, // default value
|
||||
});
|
||||
deleted: false, // starting value
|
||||
scored: false,
|
||||
};
|
||||
|
||||
const returnedWebhook = await user.put(`/user/webhook/${webhookToUpdate.id}`, { type, options });
|
||||
|
||||
await user.sync();
|
||||
|
||||
const savedWebhook = user.webhooks.find(hook => webhookToUpdate.id === hook.id);
|
||||
|
||||
expect(returnedWebhook.options).to.eql(expected);
|
||||
expect(savedWebhook.options).to.eql(expected);
|
||||
});
|
||||
|
||||
it('errors if taskActivity option is not a boolean', async () => {
|
||||
|
||||
@@ -53,5 +53,11 @@ describe('POST /user/auth/verify-display-name', async () => {
|
||||
displayName: 'this is a very long display name over 30 characters',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueLength')] });
|
||||
});
|
||||
|
||||
it('errors if display name contains a newline', async () => {
|
||||
await expect(user.post(ENDPOINT, {
|
||||
displayName: 'namecontainsnewline\n',
|
||||
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueNewline')] });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -113,6 +113,30 @@ describe('shared.ops.feed', () => {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not allow bulk-feeding query amount above food owned', done => {
|
||||
user.items.pets['Wolf-Base'] = 5;
|
||||
user.items.food.Meat = 6;
|
||||
try {
|
||||
feed(user, { params: { pet: 'Wolf-Base', food: 'Meat' }, query: { amount: 8 } });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('notEnoughFood'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not allow bulk-over-feeding pet', done => {
|
||||
user.items.pets['Wolf-Base'] = 45;
|
||||
user.items.food.Meat = 3;
|
||||
try {
|
||||
feed(user, { params: { pet: 'Wolf-Base', food: 'Meat' }, query: { amount: 2 } });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('tooMuchFood'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
context('successful feeding', () => {
|
||||
@@ -188,6 +212,61 @@ describe('shared.ops.feed', () => {
|
||||
expect(user.items.pets['Wolf-Base']).to.equal(7);
|
||||
});
|
||||
|
||||
it('evolves the pet into a mount when feeding user.items.pets[pet] >= 50 preferred food (bulk)', () => {
|
||||
user.items.pets['Wolf-Base'] = 5;
|
||||
user.items.food.Meat = 10;
|
||||
user.items.currentPet = 'Wolf-Base';
|
||||
|
||||
const pet = content.petInfo['Wolf-Base'];
|
||||
|
||||
const [data, message] = feed(user, { params: { pet: 'Wolf-Base', food: 'Meat' }, query: { amount: 9 } });
|
||||
expect(data).to.eql(user.items.pets['Wolf-Base']);
|
||||
expect(message).to.eql(i18n.t('messageEvolve', {
|
||||
egg: pet.text(),
|
||||
}));
|
||||
|
||||
expect(user.items.food.Meat).to.equal(1);
|
||||
expect(user.items.pets['Wolf-Base']).to.equal(-1);
|
||||
expect(user.items.mounts['Wolf-Base']).to.equal(true);
|
||||
expect(user.items.currentPet).to.equal('');
|
||||
});
|
||||
|
||||
it('evolves the pet into a mount when feeding user.items.pets[pet] >= 50 wrong food (bulk)', () => {
|
||||
user.items.pets['Wolf-Base'] = 5;
|
||||
user.items.food.Milk = 25;
|
||||
user.items.currentPet = 'Wolf-Base';
|
||||
|
||||
const pet = content.petInfo['Wolf-Base'];
|
||||
|
||||
const [data, message] = feed(user, { params: { pet: 'Wolf-Base', food: 'Milk' }, query: { amount: 23 } });
|
||||
expect(data).to.eql(user.items.pets['Wolf-Base']);
|
||||
expect(message).to.eql(i18n.t('messageEvolve', {
|
||||
egg: pet.text(),
|
||||
}));
|
||||
expect(user.items.food.Milk).to.equal(2);
|
||||
expect(user.items.pets['Wolf-Base']).to.equal(-1);
|
||||
expect(user.items.mounts['Wolf-Base']).to.equal(true);
|
||||
expect(user.items.currentPet).to.equal('');
|
||||
});
|
||||
|
||||
it('does not like the food (bulk low food) ', () => {
|
||||
user.items.pets['Wolf-Base'] = 5;
|
||||
user.items.food.Milk = 5;
|
||||
|
||||
const food = content.food.Milk;
|
||||
const pet = content.petInfo['Wolf-Base'];
|
||||
|
||||
const [data, message] = feed(user, { params: { pet: 'Wolf-Base', food: 'Milk' }, query: { amount: 5 } });
|
||||
expect(data).to.eql(user.items.pets['Wolf-Base']);
|
||||
expect(message).to.eql(i18n.t('messageDontEnjoyFood', {
|
||||
egg: pet.text(),
|
||||
foodText: food.textThe(),
|
||||
}));
|
||||
|
||||
expect(user.items.food.Milk).to.equal(0);
|
||||
expect(user.items.pets['Wolf-Base']).to.equal(15);
|
||||
});
|
||||
|
||||
it('awards All Your Base achievement', () => {
|
||||
user.items.pets['Wolf-Spooky'] = 5;
|
||||
user.items.food.Milk = 2;
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@amplitude/ua-parser-js": {
|
||||
"version": "0.7.20",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/ua-parser-js/-/ua-parser-js-0.7.20.tgz",
|
||||
"integrity": "sha512-bmW++BLt1Hg+4HCExLXP+0Jhgy2eTsEevqkVc5o4yYbgwdP/gV3gEQXzyVrMVlWWNLgph/tFIkf5PVlSpCELEg=="
|
||||
"version": "0.7.24",
|
||||
"resolved": "https://registry.npmjs.org/@amplitude/ua-parser-js/-/ua-parser-js-0.7.24.tgz",
|
||||
"integrity": "sha512-VbQuJymJ20WEw0HtI2np7EdC3NJGUWi8+Xdbc7uk8WfMIF308T0howpzkQ3JFMN7ejnrcSM/OyNGveeE3TP3TA=="
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.5.5",
|
||||
@@ -5859,11 +5859,11 @@
|
||||
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM="
|
||||
},
|
||||
"amplitude-js": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-6.2.0.tgz",
|
||||
"integrity": "sha512-l+00XubD0ZJHcWUBMzV6JrtXs1JhsqO/qGoDbYLtljtgup/cCclOAuirwQJMtYLRP6vEmstGjZtq1Ew9nWnzug==",
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-7.1.0.tgz",
|
||||
"integrity": "sha512-X2Jf4piKGhoPcGow1AZAw73s5tzP6OZLiUkLsnus6Mrx7zZwmCsxIuvhzB8VRnNGUE71m1zemSu0gEmf8A4qnw==",
|
||||
"requires": {
|
||||
"@amplitude/ua-parser-js": "0.7.20",
|
||||
"@amplitude/ua-parser-js": "0.7.24",
|
||||
"blueimp-md5": "^2.10.0",
|
||||
"query-string": "5"
|
||||
},
|
||||
@@ -13253,9 +13253,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
"version": "4.17.19",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
||||
},
|
||||
"lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
@@ -17161,9 +17161,9 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.26.9",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.26.9.tgz",
|
||||
"integrity": "sha512-t8AkRVi+xvba4yZiLWkJdgJHBFCB3Dh4johniQkPy9ywkgFHNasXFEFP+RG/F6LhQ+aoE4aX+IorIWQjS0esVw==",
|
||||
"version": "1.26.10",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.26.10.tgz",
|
||||
"integrity": "sha512-bzN0uvmzfsTvjz0qwccN1sPm2HxxpNI/Xa+7PlUEMS+nQvbyuEK7Y0qFqxlPHhiNHb1Ze8WQJtU31olMObkAMw==",
|
||||
"requires": {
|
||||
"chokidar": ">=2.0.0 <4.0.0"
|
||||
}
|
||||
@@ -19791,9 +19791,9 @@
|
||||
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw=="
|
||||
},
|
||||
"vuedraggable": {
|
||||
"version": "2.23.2",
|
||||
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.23.2.tgz",
|
||||
"integrity": "sha512-PgHCjUpxEAEZJq36ys49HfQmXglattf/7ofOzUrW2/rRdG7tu6fK84ir14t1jYv4kdXewTEa2ieKEAhhEMdwkQ==",
|
||||
"version": "2.24.0",
|
||||
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.24.0.tgz",
|
||||
"integrity": "sha512-IlslPpc+iZ2zPNSJbydFZIDrE+don5u+Nc/bjT2YaF+Azidc+wxxJKfKT0NwE68AKk0syb0YbZneAcnynqREZQ==",
|
||||
"requires": {
|
||||
"sortablejs": "^1.10.1"
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@vue/cli-plugin-unit-mocha": "^4.4.6",
|
||||
"@vue/cli-service": "^4.4.6",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"amplitude-js": "^6.2.0",
|
||||
"amplitude-js": "^7.1.0",
|
||||
"axios": "^0.19.2",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
@@ -41,10 +41,10 @@
|
||||
"inspectpack": "^4.5.2",
|
||||
"intro.js": "^2.9.3",
|
||||
"jquery": "^3.5.1",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash": "^4.17.19",
|
||||
"moment": "^2.27.0",
|
||||
"nconf": "^0.10.0",
|
||||
"sass": "^1.26.9",
|
||||
"sass": "^1.26.10",
|
||||
"sass-loader": "^8.0.2",
|
||||
"smartbanner.js": "^1.16.0",
|
||||
"svg-inline-loader": "^0.8.2",
|
||||
@@ -58,7 +58,7 @@
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
"vue-router": "^3.3.4",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuedraggable": "^2.23.2",
|
||||
"vuedraggable": "^2.24.0",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||
"webpack": "^4.43.0"
|
||||
}
|
||||
|
||||
@@ -369,7 +369,6 @@ export default {
|
||||
|
||||
const isApiCall = url.indexOf('api/v4') !== -1;
|
||||
const userV = response.data && response.data.userV;
|
||||
const isCron = url.indexOf('/api/v4/cron') === 0 && method === 'post';
|
||||
|
||||
if (this.isUserLoaded && isApiCall && userV) {
|
||||
const oldUserV = this.user._v;
|
||||
@@ -381,9 +380,14 @@ export default {
|
||||
// exclude chat seen requests because with real time chat they would be too many
|
||||
const isChatSeen = url.indexOf('/chat/seen') !== -1 && method === 'post';
|
||||
// exclude POST /api/v4/cron because the user is synced automatically after cron runs
|
||||
const isCron = url.indexOf('/api/v4/cron') === 0 && method === 'post';
|
||||
// exclude skills casting as they already return the synced user
|
||||
const isCast = url.indexOf('/api/v4/user/class/cast') !== -1 && method === 'post';
|
||||
|
||||
// Something has changed on the user object that was not tracked here, sync the user
|
||||
if (userV - oldUserV > 1 && !isCron && !isChatSeen && !isUserSync && !isTasksSync) {
|
||||
if (
|
||||
userV - oldUserV > 1 && !isCron && !isChatSeen && !isUserSync && !isTasksSync && !isCast
|
||||
) {
|
||||
Promise.all([
|
||||
this.$store.dispatch('user:fetch', { forceLoad: true }),
|
||||
this.$store.dispatch('tasks:fetchUserTasks', { forceLoad: true }),
|
||||
|
||||
@@ -1,78 +1,78 @@
|
||||
.promo_aquatic_amigos_bundle {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -622px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_armoire_backgrounds_202007 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -340px -524px;
|
||||
background-position: -812px 0px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_202007 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -424px -735px;
|
||||
background-position: -812px -296px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_orcas {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -812px -444px;
|
||||
width: 219px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_sand_sculpture_potions {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -735px;
|
||||
background-position: -812px -148px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_seafoam {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -473px;
|
||||
width: 425px;
|
||||
height: 148px;
|
||||
}
|
||||
.promo_splashy_skins {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -409px -337px;
|
||||
background-position: -409px -286px;
|
||||
width: 375px;
|
||||
height: 186px;
|
||||
}
|
||||
.customize-option.promo_splashy_skins {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -434px -352px;
|
||||
background-position: -434px -301px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_summer_splash_2019 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -337px;
|
||||
background-position: 0px -286px;
|
||||
width: 408px;
|
||||
height: 186px;
|
||||
}
|
||||
.promo_summer_splash_2020 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -445px 0px;
|
||||
background-position: -367px 0px;
|
||||
width: 444px;
|
||||
height: 198px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1032px -389px;
|
||||
background-position: -1095px -296px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.scene_achievement {
|
||||
.scene_cathb {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -524px;
|
||||
width: 339px;
|
||||
height: 210px;
|
||||
background-position: -812px -592px;
|
||||
width: 141px;
|
||||
height: 167px;
|
||||
}
|
||||
.scene_hat_guild {
|
||||
.scene_tools {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 444px;
|
||||
height: 336px;
|
||||
}
|
||||
.scene_hiking {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -890px 0px;
|
||||
width: 258px;
|
||||
height: 258px;
|
||||
}
|
||||
.scene_nakonana {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -890px -389px;
|
||||
width: 141px;
|
||||
height: 169px;
|
||||
}
|
||||
.scene_strength {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -890px -259px;
|
||||
width: 192px;
|
||||
height: 129px;
|
||||
width: 366px;
|
||||
height: 285px;
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
}
|
||||
.quest_amber {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -660px;
|
||||
background-position: -220px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_armadillo {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -672px;
|
||||
background-position: -440px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -48,13 +48,13 @@
|
||||
}
|
||||
.quest_axolotl {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -232px;
|
||||
background-position: 0px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_badger {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px 0px;
|
||||
background-position: -220px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -72,7 +72,7 @@
|
||||
}
|
||||
.quest_bronze {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -452px;
|
||||
background-position: -440px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -84,13 +84,13 @@
|
||||
}
|
||||
.quest_butterfly {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -452px;
|
||||
background-position: -660px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_cheetah {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px 0px;
|
||||
background-position: -660px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -102,7 +102,7 @@
|
||||
}
|
||||
.quest_dilatory {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -440px;
|
||||
background-position: -220px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -114,31 +114,31 @@
|
||||
}
|
||||
.quest_dilatoryDistress2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1757px -573px;
|
||||
background-position: -1757px -422px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_dilatoryDistress3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -672px;
|
||||
background-position: -440px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_dilatory_derby {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -220px;
|
||||
background-position: 0px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_dolphin {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -672px;
|
||||
background-position: -660px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_dustbunnies {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px 0px;
|
||||
background-position: -880px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -156,25 +156,25 @@
|
||||
}
|
||||
.quest_evilsanta2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -440px;
|
||||
background-position: -880px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_falcon {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px 0px;
|
||||
background-position: -880px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_ferret {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -892px;
|
||||
background-position: 0px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_fluorite {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -892px;
|
||||
background-position: -220px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -186,13 +186,13 @@
|
||||
}
|
||||
.quest_ghost_stag {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -892px;
|
||||
background-position: -440px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_goldenknight1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -892px;
|
||||
background-position: -660px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -210,19 +210,19 @@
|
||||
}
|
||||
.quest_gryphon {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -1332px;
|
||||
background-position: -223px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_guineapig {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px -440px;
|
||||
background-position: -880px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_harpy {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px -660px;
|
||||
background-position: -1100px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -234,79 +234,79 @@
|
||||
}
|
||||
.quest_hippo {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -1112px;
|
||||
background-position: -1100px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_horse {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -1112px;
|
||||
background-position: -1100px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_kangaroo {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -1112px;
|
||||
background-position: -1100px -660px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_kraken {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -223px -1332px;
|
||||
background-position: -440px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_lostMasterclasser1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px -880px;
|
||||
background-position: 0px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_lostMasterclasser2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -892px;
|
||||
background-position: -220px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_lostMasterclasser3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -220px;
|
||||
background-position: -440px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_mayhemMistiflying1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1757px -422px;
|
||||
background-position: -1757px -573px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_mayhemMistiflying2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -452px;
|
||||
background-position: -660px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_mayhemMistiflying3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -232px;
|
||||
background-position: -880px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_monkey {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px 0px;
|
||||
background-position: -1100px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moon1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px -651px;
|
||||
background-position: -1540px 0px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_moon2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -1112px;
|
||||
background-position: -1320px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
@@ -318,25 +318,25 @@
|
||||
}
|
||||
.quest_moonstone1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -892px;
|
||||
background-position: -1320px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -452px;
|
||||
background-position: -1320px -660px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -232px;
|
||||
background-position: -1320px -880px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_nudibranch {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px -434px;
|
||||
background-position: -1540px -217px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
@@ -348,13 +348,13 @@
|
||||
}
|
||||
.quest_owl {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -672px;
|
||||
background-position: 0px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_peacock {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px 0px;
|
||||
background-position: -1540px -434px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
@@ -366,25 +366,25 @@
|
||||
}
|
||||
.quest_pterodactyl {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px 0px;
|
||||
background-position: -220px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_rat {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -672px;
|
||||
background-position: -440px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_robot {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -220px;
|
||||
background-position: -660px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_rock {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px -217px;
|
||||
background-position: -1540px -651px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
.Mount_Icon_Cactus-Frost {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -82px 0px;
|
||||
background-position: 0px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Mount_Icon_Cactus-Ghost {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: -82px -1100px;
|
||||
background-position: -82px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
@@ -996,7 +996,7 @@
|
||||
}
|
||||
.Mount_Icon_FlyingPig-Golden {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-23.png');
|
||||
background-position: 0px 0px;
|
||||
background-position: -82px -1100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
|
||||
@@ -1,150 +1,150 @@
|
||||
.Pet-Dragon-Ember {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -492px -100px;
|
||||
background-position: -82px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Fairy {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -82px -1103px;
|
||||
background-position: -164px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Floral {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -164px 0px;
|
||||
background-position: 0px -103px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Fluorite {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: 0px -103px;
|
||||
background-position: -82px -103px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Frost {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -82px -103px;
|
||||
background-position: -164px -103px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Ghost {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -164px -103px;
|
||||
background-position: -246px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Glass {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -246px 0px;
|
||||
background-position: -246px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Glow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -246px -100px;
|
||||
background-position: 0px -203px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Golden {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: 0px -203px;
|
||||
background-position: -82px -203px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Holly {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -82px -203px;
|
||||
background-position: -164px -203px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Hydra {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -164px -203px;
|
||||
background-position: -246px -203px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-IcySnow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -246px -203px;
|
||||
background-position: -328px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Peppermint {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -328px 0px;
|
||||
background-position: -328px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Rainbow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -328px -100px;
|
||||
background-position: -328px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Red {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -328px -200px;
|
||||
background-position: 0px -303px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-RoseQuartz {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: 0px -303px;
|
||||
background-position: -82px -303px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-RoyalPurple {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -82px -303px;
|
||||
background-position: -164px -303px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Ruby {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -164px -303px;
|
||||
background-position: -246px -303px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-SandSculpture {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -246px -303px;
|
||||
background-position: -328px -303px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Shade {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -328px -303px;
|
||||
background-position: -410px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Shadow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -410px 0px;
|
||||
background-position: -410px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Shimmer {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -410px -100px;
|
||||
background-position: -410px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Silver {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -410px -200px;
|
||||
background-position: -410px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Skeleton {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -410px -300px;
|
||||
background-position: -492px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Spooky {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -492px 0px;
|
||||
background-position: -492px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
@@ -996,7 +996,7 @@
|
||||
}
|
||||
.Pet-Gryphon-Shade {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-26.png');
|
||||
background-position: -82px 0px;
|
||||
background-position: -82px -1103px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
.Pet-PandaCub-Glow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -82px 0px;
|
||||
background-position: 0px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-PandaCub-Golden {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -82px -1100px;
|
||||
background-position: -82px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
@@ -996,7 +996,7 @@
|
||||
}
|
||||
.Pet-Snail-CottonCandyBlue {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: 0px 0px;
|
||||
background-position: -82px -1100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
.Pet-Wolf-Dessert {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-28.png');
|
||||
background-position: -82px 0px;
|
||||
background-position: 0px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Ember {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-28.png');
|
||||
background-position: -164px -500px;
|
||||
background-position: -82px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
@@ -252,19 +252,19 @@
|
||||
}
|
||||
.Pet-Yarn-Zombie {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-28.png');
|
||||
background-position: 0px 0px;
|
||||
background-position: -164px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet_HatchingPotion_Amber {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-28.png');
|
||||
background-position: -315px -500px;
|
||||
background-position: -246px -500px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Aquatic {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-28.png');
|
||||
background-position: -552px -600px;
|
||||
background-position: -315px -500px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
@@ -384,7 +384,7 @@
|
||||
}
|
||||
.Pet_HatchingPotion_IcySnow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-28.png');
|
||||
background-position: -246px -500px;
|
||||
background-position: -552px -600px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
@@ -12,25 +12,25 @@
|
||||
}
|
||||
.hair_bangs_2_pumpkin {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-3.png');
|
||||
background-position: -91px 0px;
|
||||
background-position: 0px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_bangs_2_pumpkin {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-3.png');
|
||||
background-position: -116px -15px;
|
||||
background-position: -25px -15px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.hair_bangs_2_purple {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-3.png');
|
||||
background-position: -728px -1092px;
|
||||
background-position: -91px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_bangs_2_purple {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-3.png');
|
||||
background-position: -753px -1107px;
|
||||
background-position: -116px -15px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
@@ -1500,13 +1500,13 @@
|
||||
}
|
||||
.hair_base_10_white {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-3.png');
|
||||
background-position: 0px 0px;
|
||||
background-position: -728px -1092px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_10_white {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-3.png');
|
||||
background-position: -25px -15px;
|
||||
background-position: -753px -1107px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
@@ -12,25 +12,25 @@
|
||||
}
|
||||
.hair_base_15_brown {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-4.png');
|
||||
background-position: -91px 0px;
|
||||
background-position: 0px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_15_brown {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-4.png');
|
||||
background-position: -116px -0px;
|
||||
background-position: -25px -0px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.hair_base_15_candycane {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-4.png');
|
||||
background-position: -728px -1092px;
|
||||
background-position: -91px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_15_candycane {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-4.png');
|
||||
background-position: -753px -1092px;
|
||||
background-position: -116px -0px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
@@ -1980,13 +1980,13 @@
|
||||
}
|
||||
.hair_base_19_holly {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-4.png');
|
||||
background-position: 0px 0px;
|
||||
background-position: -728px -1092px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_19_holly {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-4.png');
|
||||
background-position: -25px -0px;
|
||||
background-position: -753px -1092px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
@@ -12,25 +12,25 @@
|
||||
}
|
||||
.hair_base_4_porange2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-5.png');
|
||||
background-position: -91px 0px;
|
||||
background-position: 0px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_4_porange2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-5.png');
|
||||
background-position: -116px -15px;
|
||||
background-position: -25px -15px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.hair_base_4_ppink {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-5.png');
|
||||
background-position: -728px -1092px;
|
||||
background-position: -91px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_4_ppink {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-5.png');
|
||||
background-position: -753px -1107px;
|
||||
background-position: -116px -15px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
@@ -1980,13 +1980,13 @@
|
||||
}
|
||||
.hair_base_8_pyellow2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-5.png');
|
||||
background-position: 0px 0px;
|
||||
background-position: -728px -1092px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.customize-option.hair_base_8_pyellow2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-5.png');
|
||||
background-position: -25px -15px;
|
||||
background-position: -753px -1107px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 468 KiB After Width: | Height: | Size: 468 KiB |
|
Before Width: | Height: | Size: 592 KiB After Width: | Height: | Size: 593 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 384 KiB After Width: | Height: | Size: 384 KiB |
|
Before Width: | Height: | Size: 316 KiB After Width: | Height: | Size: 320 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 153 KiB |
@@ -521,9 +521,12 @@ export default {
|
||||
categories () {
|
||||
if (this.shop.categories) {
|
||||
this.shop.categories.forEach(category => {
|
||||
this.$set(this.viewOptions, category.identifier, {
|
||||
selected: false,
|
||||
});
|
||||
// do not reset the viewOptions if already set once
|
||||
if (typeof this.viewOptions[category.identifier] === 'undefined') {
|
||||
this.$set(this.viewOptions, category.identifier, {
|
||||
selected: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return this.shop.categories;
|
||||
|
||||
@@ -379,9 +379,12 @@ export default {
|
||||
normalGroups.push(setCategory);
|
||||
|
||||
normalGroups.forEach(category => {
|
||||
this.$set(this.viewOptions, category.identifier, {
|
||||
selected: false,
|
||||
});
|
||||
// do not reset the viewOptions if already set once
|
||||
if (typeof this.viewOptions[category.identifier] === 'undefined') {
|
||||
this.$set(this.viewOptions, category.identifier, {
|
||||
selected: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return normalGroups;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<div
|
||||
v-if="label !== 'skip'"
|
||||
:id="key"
|
||||
class="box"
|
||||
class="gear box"
|
||||
:class="{white: equippedItems[key] && equippedItems[key].indexOf('base_0') === -1}"
|
||||
>
|
||||
<div :class="`shop_${equippedItems[key]}`"></div>
|
||||
@@ -39,9 +39,12 @@
|
||||
:user="user"
|
||||
/>
|
||||
</b-popover>
|
||||
<h3 v-if="label !== 'skip'">
|
||||
<span
|
||||
v-if="label !== 'skip'"
|
||||
class="gear-label"
|
||||
>
|
||||
{{ label }}
|
||||
</h3>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,7 +63,7 @@
|
||||
<div
|
||||
v-if="label !== 'skip'"
|
||||
:id="key + 'C'"
|
||||
class="box"
|
||||
class="gear box"
|
||||
:class="{white: costumeItems[key] && costumeItems[key].indexOf('base_0') === -1}"
|
||||
>
|
||||
<div :class="`shop_${costumeItems[key]}`"></div>
|
||||
@@ -68,7 +71,7 @@
|
||||
<!-- Show background on 8th tile rather than a piece of equipment.-->
|
||||
<div
|
||||
v-if="label === 'skip'"
|
||||
class="box"
|
||||
class="gear box"
|
||||
:class="{white: user.preferences.background}"
|
||||
style="overflow:hidden"
|
||||
>
|
||||
@@ -91,12 +94,18 @@
|
||||
:user="user"
|
||||
/>
|
||||
</b-popover>
|
||||
<h3 v-if="label !== 'skip'">
|
||||
<span
|
||||
v-if="label !== 'skip'"
|
||||
class="gear-label"
|
||||
>
|
||||
{{ label }}
|
||||
</h3>
|
||||
<h3 v-else>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="gear-label"
|
||||
>
|
||||
{{ $t('background') }}
|
||||
</h3>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -667,9 +676,9 @@ export default {
|
||||
}
|
||||
|
||||
.item-wrapper {
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pet-mount-row {
|
||||
@@ -684,4 +693,34 @@ export default {
|
||||
.save-row {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
|
||||
.gear.box {
|
||||
vertical-align: top;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.gear-label {
|
||||
margin: 0 auto;
|
||||
margin-top: 0.5rem;
|
||||
min-height: 1rem;
|
||||
font-family: Roboto;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
text-align: center;
|
||||
color: $gray-200;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
|
||||
// the following 4 lines are needed for the 2 line clamp
|
||||
// the non-prefixes not supported "anywhere" but these "-webkit"-ones are
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
|
||||
// breaks the long words without a space
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -137,6 +137,11 @@ export default {
|
||||
targetId,
|
||||
pinType: spell.pinType,
|
||||
});
|
||||
|
||||
if (apiResult.data.data.user) {
|
||||
Object.assign(this.$store.state.user.data, apiResult.data.data.user);
|
||||
}
|
||||
|
||||
let msg = '';
|
||||
|
||||
switch (type) {
|
||||
@@ -166,12 +171,6 @@ export default {
|
||||
break;
|
||||
}
|
||||
|
||||
if (spell.pinType === 'card') {
|
||||
const newUserGp = apiResult.data.data.user.stats.gp;
|
||||
this.$store.state.user.data.stats.gp = newUserGp;
|
||||
}
|
||||
|
||||
|
||||
this.markdown(msg); // @TODO: mardown directive?
|
||||
|
||||
const questProgress = this.questProgress() - beforeQuestProgress;
|
||||
|
||||
@@ -656,6 +656,7 @@ export default {
|
||||
backer: data.backer,
|
||||
contributor: data.contributor,
|
||||
userStyles: data.userStyles,
|
||||
canReceive: true,
|
||||
};
|
||||
|
||||
this.$store.state.privateMessageOptions = {};
|
||||
@@ -896,6 +897,7 @@ export default {
|
||||
username: this.user.auth.local.username,
|
||||
contributor: this.user.contributor,
|
||||
backer: this.user.backer,
|
||||
canReceive: true,
|
||||
});
|
||||
|
||||
// Remove the placeholder message
|
||||
|
||||
@@ -290,7 +290,7 @@
|
||||
"approvalTitle": "<%= userName %> has completed <%= type %>: \"<%= text %>\"",
|
||||
"confirmTaskApproval": "Do you want to reward <%= username %> for completing this task?",
|
||||
"groupSubscriptionPrice": "$9 every month + $3 a month for every additional group member",
|
||||
"groupAdditionalUserCost": "+$3.00/month/user",
|
||||
"groupAdditionalUserCost": " +$3.00/month/user",
|
||||
"groupBenefitsTitle": "How a group plan can help you",
|
||||
"groupBenefitsDescription": "We've just launched the beta version of our group plans! Upgrading to a group plan unlocks some unique features to optimize the social side of Habitica.",
|
||||
"groupBenefitOneTitle": "Create a shared task list",
|
||||
@@ -329,7 +329,7 @@
|
||||
"approvalRequested": "Approval Requested",
|
||||
"refreshApprovals": "Refresh Approvals",
|
||||
"refreshGroupTasks": "Refresh Group Tasks",
|
||||
"claimedBy": "Claimed by: <%= claimingUsers %>",
|
||||
"claimedBy": "\n\nClaimed by: <%= claimingUsers %>",
|
||||
"cantDeleteAssignedGroupTasks": "Can't delete group tasks that are assigned to you.",
|
||||
"confirmGuildPlanCreation": "Create this group?",
|
||||
"groupPlanUpgraded": "<strong><%= groupName %></strong> was upgraded to a Group Plan!",
|
||||
@@ -480,4 +480,4 @@
|
||||
"recurringCompletion": "None - Group task does not complete",
|
||||
"singleCompletion": "Single - Completes when any assigned user finishes",
|
||||
"allAssignedCompletion": "All - Completes when all assigned users finish"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"messageLikesFood": "<%= egg %> really likes <%= foodText %>!",
|
||||
"messageDontEnjoyFood": "<%= egg %> eats <%= foodText %> but doesn't seem to enjoy it.",
|
||||
"messageBought": "Bought <%= itemText %>",
|
||||
"messageEquipped": "<%= itemText %> equipped.",
|
||||
"messageEquipped": " <%= itemText %> equipped.",
|
||||
"messageUnEquipped": "<%= itemText %> unequipped.",
|
||||
"messageMissingEggPotion": "You're missing either that egg or that potion",
|
||||
"messageInvalidEggPotionCombo": "You can't hatch Quest Pet Eggs with Magic Hatching Potions! Try a different egg.",
|
||||
@@ -63,4 +63,4 @@
|
||||
"beginningOfConversation": "This is the beginning of your conversation with <%= userName %>. Remember to be kind, respectful, and follow the Community Guidelines!",
|
||||
"messageDeletedUser": "Sorry, this user has deleted their account.",
|
||||
"messageMissingDisplayName": "Missing display name."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@
|
||||
"approvalTitle": "<%= userName %> has completed <%= type %>: \"<%= text %>\"",
|
||||
"confirmTaskApproval": "Do you want to reward <%= username %> for completing this task?",
|
||||
"groupSubscriptionPrice": "$9 every month + $3 a month for every additional group member",
|
||||
"groupAdditionalUserCost": "+$3.00/month/user",
|
||||
"groupAdditionalUserCost": " +$3.00/month/user",
|
||||
"groupBenefitsTitle": "How a group plan can help you",
|
||||
"groupBenefitsDescription": "We've just launched the beta version of our group plans! Upgrading to a group plan unlocks some unique features to optimize the social side of Habitica.",
|
||||
"groupBenefitOneTitle": "Create a shared task list",
|
||||
@@ -329,7 +329,7 @@
|
||||
"approvalRequested": "Approval Requested",
|
||||
"refreshApprovals": "Refresh Approvals",
|
||||
"refreshGroupTasks": "Refresh Group Tasks",
|
||||
"claimedBy": "Claimed by: <%= claimingUsers %>",
|
||||
"claimedBy": "\n\nClaimed by: <%= claimingUsers %>",
|
||||
"cantDeleteAssignedGroupTasks": "Can't delete group tasks that are assigned to you.",
|
||||
"confirmGuildPlanCreation": "Create this group?",
|
||||
"groupPlanUpgraded": "<strong><%= groupName %></strong> was upgraded to a Group Plan!",
|
||||
@@ -480,4 +480,4 @@
|
||||
"recurringCompletion": "None - Group task does not complete",
|
||||
"singleCompletion": "Single - Completes when any assigned user finishes",
|
||||
"allAssignedCompletion": "All - Completes when all assigned users finish"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"messageLikesFood": "<%= egg %> really likes <%= foodText %>!",
|
||||
"messageDontEnjoyFood": "<%= egg %> eats <%= foodText %> but doesn't seem to enjoy it.",
|
||||
"messageBought": "Bought <%= itemText %>",
|
||||
"messageEquipped": "<%= itemText %> equipped.",
|
||||
"messageEquipped": " <%= itemText %> equipped.",
|
||||
"messageUnEquipped": "<%= itemText %> unequipped.",
|
||||
"messageMissingEggPotion": "You're missing either that egg or that potion",
|
||||
"messageInvalidEggPotionCombo": "You can't hatch Quest Pet Eggs with Magic Hatching Potions! Try a different egg.",
|
||||
@@ -63,4 +63,4 @@
|
||||
"beginningOfConversation": "This is the beginning of your conversation with <%= userName %>. Remember to be kind, respectful, and follow the Community Guidelines!",
|
||||
"messageDeletedUser": "Sorry, this user has deleted their account.",
|
||||
"messageMissingDisplayName": "Missing display name."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
"backgroundDeepMineNotes": "اعثر علي معادن ثمينة من المنغم الغويط",
|
||||
"backgroundRainforestText": "الغابة الإستوائية",
|
||||
"backgroundRainforestNotes": "إستكشف الغابة الإستوائية",
|
||||
"backgroundStoneCircleText": "دائرة الحجر ",
|
||||
"backgroundStoneCircleText": "دائرة الحجر",
|
||||
"backgroundStoneCircleNotes": "ألقي تعويذة في دائرة الحجر",
|
||||
"backgrounds042016": "طقم 23: صدر في أبريل 2016",
|
||||
"backgroundArcheryRangeText": "مدى الرماية بالأسهم",
|
||||
@@ -200,7 +200,7 @@
|
||||
"backgroundFarmhouseNotes": "قل مرحبا للحيوانات في طريقك إلى البيت الريفي.",
|
||||
"backgroundOrchardText": "بستان",
|
||||
"backgroundOrchardNotes": "اقطف فاكهة ناضجة من البستان.",
|
||||
"backgrounds102016": " طقم 29: صدر في أكتوبر 2016",
|
||||
"backgrounds102016": "طقم 29: صدر في أكتوبر 2016",
|
||||
"backgroundSpiderWebText": "شبكة عنكبوت",
|
||||
"backgroundSpiderWebNotes": "اعلق في شبكة عنكبوت",
|
||||
"backgroundStrangeSewersText": "مجاري غريبة",
|
||||
@@ -227,7 +227,7 @@
|
||||
"backgroundRedNotes": "خلفية حمراء خطيرة.",
|
||||
"backgroundYellowText": "أصفر",
|
||||
"backgroundYellowNotes": "خلفية صفراء لذيذة.",
|
||||
"backgrounds122016": " طقم 31: صدر فى ديسمبر 2016",
|
||||
"backgrounds122016": "طقم 31: صدر فى ديسمبر 2016",
|
||||
"backgroundShimmeringIcePrismText": "بلورات الثلج المتلألئة",
|
||||
"backgroundShimmeringIcePrismNotes": "الرقص خلال بلورات الثلج المتلألئة.",
|
||||
"backgroundWinterFireworksText": "ألعاب نارية لفصل الشتاء",
|
||||
@@ -241,7 +241,7 @@
|
||||
"backgroundSparklingSnowflakeNotes": "تزلق على ندفة ثلج متلألئة.",
|
||||
"backgroundStoikalmVolcanoesText": "براكين ستويكالم",
|
||||
"backgroundStoikalmVolcanoesNotes": "استكشف براكين ستويكالم.",
|
||||
"backgrounds022017": " طقم 33: صدر في فبراير 2017",
|
||||
"backgrounds022017": "طقم 33: صدر في فبراير 2017",
|
||||
"backgroundBellTowerText": "برج الجرس",
|
||||
"backgroundBellTowerNotes": "تسلق برج الجرس.",
|
||||
"backgroundTreasureRoomText": "غرفة الكنز",
|
||||
@@ -262,7 +262,7 @@
|
||||
"backgroundGiantBirdhouseNotes": "إجثم في العش الضخم.",
|
||||
"backgroundMistShroudedMountainText": "الجبل الضبابي",
|
||||
"backgroundMistShroudedMountainNotes": "اصعد قمة الجبل الضبابي.",
|
||||
"backgrounds052017": " طقم 36: صدر في مايو 2017",
|
||||
"backgrounds052017": "طقم 36: صدر في مايو 2017",
|
||||
"backgroundGuardianStatuesText": "تمثال القيم",
|
||||
"backgroundGuardianStatuesNotes": "قف معترضاً اما تمثال القيم.",
|
||||
"backgroundHabitCityStreetsText": "شوارع مدينة العادة",
|
||||
@@ -276,35 +276,35 @@
|
||||
"backgroundOceanSunriseNotes": "تعجب من شروق المحيط.",
|
||||
"backgroundSandcastleText": "قلعة رملية",
|
||||
"backgroundSandcastleNotes": "احكم قلعة رملية.",
|
||||
"backgrounds072017": " طقم 38: صدر في يوليو 2017",
|
||||
"backgrounds072017": "طقم 38: صدر في يوليو 2017",
|
||||
"backgroundGiantSeashellText": "صدفة بحر عملاقة",
|
||||
"backgroundGiantSeashellNotes": "استلقِ في صدفة بحر عملاقة.",
|
||||
"backgroundKelpForestText": "غابة عشب البحر",
|
||||
"backgroundKelpForestNotes": "اسبح خلال غابة عشب البحر.",
|
||||
"backgroundMidnightLakeText": "بحيرة منتصف الليل",
|
||||
"backgroundMidnightLakeNotes": "استرخي بجانب بحيرة منتصف الليل.",
|
||||
"backgrounds082017": " طقم 39: صدر في أغسطس 2017",
|
||||
"backgrounds082017": "طقم 39: صدر في أغسطس 2017",
|
||||
"backgroundBackOfGiantBeastText": "ظهر وحش عملاق",
|
||||
"backgroundBackOfGiantBeastNotes": "اركب على ظهر وحش عملاق",
|
||||
"backgroundDesertDunesText": "كثبان صحراوية",
|
||||
"backgroundDesertDunesNotes": "اكتشف كثبان الصحراء بجرأة.",
|
||||
"backgroundSummerFireworksText": "الالعاب النارية الصيفية",
|
||||
"backgroundSummerFireworksNotes": "احتفل بيوم تسمية Habitica بالألعاب النارية الصيفية!",
|
||||
"backgrounds092017": " طقم 40: صدر في سبتمبر 2017",
|
||||
"backgrounds092017": "طقم 40: صدر في سبتمبر 2017",
|
||||
"backgroundBesideWellText": "بجانب البئر",
|
||||
"backgroundBesideWellNotes": " تنزه بجانب بئر.",
|
||||
"backgroundBesideWellNotes": "تنزه بجانب بئر.",
|
||||
"backgroundGardenShedText": "سقيفة الحديقة",
|
||||
"backgroundGardenShedNotes": "إعمل في كوخ الحديقة.",
|
||||
"backgroundPixelistsWorkshopText": "Pixelist's Workshop",
|
||||
"backgroundPixelistsWorkshopNotes": "Create masterpieces in the Pixelist's Workshop.",
|
||||
"backgrounds102017": " طقم 41: صدر في أكتوبر 2017",
|
||||
"backgrounds102017": "طقم 41: صدر في أكتوبر 2017",
|
||||
"backgroundMagicalCandlesText": "شموع سحرية",
|
||||
"backgroundMagicalCandlesNotes": "تدفأ في وهج شموع سحرية.",
|
||||
"backgroundSpookyHotelText": "قندق مرعب",
|
||||
"backgroundSpookyHotelNotes": "تسلل قاعة فندق مرعب.",
|
||||
"backgroundTarPitsText": "حفر قطران",
|
||||
"backgroundTarPitsNotes": "امش على رؤوس أصابعك في حفر قطران.",
|
||||
"backgrounds112017": " طقم 42: صدر في نوفمبر 2017",
|
||||
"backgrounds112017": "طقم 42: صدر في نوفمبر 2017",
|
||||
"backgroundFiberArtsRoomText": "غرفة فنون الخياطة",
|
||||
"backgroundFiberArtsRoomNotes": "إعزل النسيج في غرفة فنون الخياطة.",
|
||||
"backgroundMidnightCastleText": "قلعة منتصف الليل",
|
||||
@@ -409,4 +409,4 @@
|
||||
"backgroundArchaeologicalDigNotes": "Unearth secrets of the ancient past at an Archaeological Dig.",
|
||||
"backgroundScribesWorkshopText": "Scribe's Workshop",
|
||||
"backgroundScribesWorkshopNotes": "Write your next great scroll in a Scribe's Workshop."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"challenge": "تحدي",
|
||||
"challengeDetails": "Challenges are community events in which players compete and earn prizes by completing a group of related tasks.",
|
||||
"brokenChaLink": "رابط التحدي لا يعمل",
|
||||
"brokenTask": "رابط التحدي معطل: هذة المهمة كانت جزء من التحدي ولكن تم حذفها. ماذا تريد ان تفعل؟ ",
|
||||
"brokenTask": "رابط التحدي معطل: هذة المهمة كانت جزء من التحدي ولكن تم حذفها. ماذا تريد ان تفعل؟",
|
||||
"keepIt": "الاحتفاظ به",
|
||||
"removeIt": "حذفه",
|
||||
"brokenChallenge": "رابط التحدي لا يعمل: هذه المهمة كانت تتبع لتحد، ولكن تم حذف التحدي (أو المجموعة). ما المراد فعله بالمهمات اليتيمة؟",
|
||||
@@ -21,7 +21,7 @@
|
||||
"deleteOrSelect": "حذف أو اختيار الفائز",
|
||||
"endChallenge": "إنهاء التحدي",
|
||||
"challengeDiscription": "تلك هى مهمات التحدى التى ستضاف إلى لوحة مهماتك عند انضمامك للتحدى. عَيّنة مهمات التحدى فى الاسفل سوف تغير الألوان وتحصل على رسوم بيانية لتظهر لك التقدم العام للمجموعة.",
|
||||
"hows": "رؤية ما يعمله المتبقون.",
|
||||
"hows": "رؤية ما يعمله المتبقون?",
|
||||
"filter": "ترشيح",
|
||||
"groups": "المجموعات",
|
||||
"noNone": "لا شيء",
|
||||
@@ -136,4 +136,4 @@
|
||||
"selectMember": "اختر عضو",
|
||||
"confirmKeepChallengeTasks": "هل تريد إبقاء مهمة التحدي؟",
|
||||
"selectParticipant": "اختر مشارك"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,8 +125,8 @@
|
||||
"characterBuild": "بنية شخصية",
|
||||
"class": "فئة",
|
||||
"experience": "الخبرة",
|
||||
"warrior": "مقاتلـ/ـة",
|
||||
"healer": "معالجـ/ـة",
|
||||
"warrior": "مقاتل/ة",
|
||||
"healer": "معالج/ة",
|
||||
"rogue": "وغد",
|
||||
"mage": "حاوي",
|
||||
"wizard": "Mage",
|
||||
@@ -225,4 +225,4 @@
|
||||
"offHand": "Off-Hand",
|
||||
"statPoints": "Stat Points",
|
||||
"pts": "pts"
|
||||
}
|
||||
}
|
||||
|
||||