Compare commits
210 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64a608e7e7 | |||
| 767f844cea | |||
| 00a686dcf6 | |||
| df860c9401 | |||
| b2d6a9474d | |||
| 5fac4a943c | |||
| 5d0be7bc72 | |||
| cc97935ffd | |||
| 6ea4d96830 | |||
| a63ba51497 | |||
| 04a7fd25a6 | |||
| 7cb045781b | |||
| f8d799d55c | |||
| 5bd9fcc99d | |||
| 84cafc4081 | |||
| d77a17112c | |||
| 4f9d97d38f | |||
| 40060e8ff5 | |||
| 508d97d374 | |||
| fd125352b7 | |||
| f8bd1be4a3 | |||
| ec37524164 | |||
| 87b9e72b56 | |||
| 962662fe7c | |||
| f63d2e47f0 | |||
| 349a1032b6 | |||
| 476131835d | |||
| 8237b7f2de | |||
| 6ee2b3690a | |||
| a08cca807a | |||
| 8c51f36784 | |||
| d35f81cdae | |||
| 1d1b25391f | |||
| ec81c02d72 | |||
| 166da3c2f8 | |||
| 23b72a673d | |||
| d22b4bb2f7 | |||
| aaebd4da77 | |||
| b128e7874e | |||
| 1e10e20a24 | |||
| b1aeb8ed87 | |||
| 2cb80e2275 | |||
| ee32e24ff2 | |||
| af40c437be | |||
| a99150c485 | |||
| 696b67204d | |||
| 4f3536e887 | |||
| 07e5bf1437 | |||
| d2ca738256 | |||
| f7983f39eb | |||
| 7c954f7073 | |||
| 82d0e737a6 | |||
| f8213aaf1b | |||
| 2335ad4167 | |||
| d84631255b | |||
| 0b352b9103 | |||
| 19b75c6257 | |||
| b66904a3a7 | |||
| cfbfec34aa | |||
| 88f28188a1 | |||
| fce5be2e8f | |||
| 1d68cbfaa2 | |||
| a44222a350 | |||
| 4d2510e322 | |||
| 3b5ed33e03 | |||
| 0a6bf92b6b | |||
| 05ae40bc2e | |||
| 57e96ea092 | |||
| ea49b5b8e0 | |||
| 11a5de714a | |||
| f74b4d3e73 | |||
| ab6fdb99af | |||
| e3efa557dd | |||
| a6cea47789 | |||
| 5a200a88bd | |||
| 545499ea0b | |||
| 3e7f4229ec | |||
| 56d5f77b6e | |||
| efdf5a2e16 | |||
| add3a2887f | |||
| 0ed7c7596a | |||
| 55a452694f | |||
| cd4d5f83ff | |||
| 8220199e49 | |||
| bd1f6918ba | |||
| 5b21e62647 | |||
| 5e76d6df21 | |||
| 7348145b7d | |||
| ba2832d21f | |||
| 688f5084a1 | |||
| d5955b8889 | |||
| 01fd17ee3f | |||
| 979497dd35 | |||
| 1d3db244ba | |||
| 80ca074352 | |||
| 6d4f9e0759 | |||
| d096695559 | |||
| a2c8b8b05c | |||
| 8750701c08 | |||
| 900676bf0a | |||
| e219daf44c | |||
| 1b12a6b51f | |||
| 8441b0a3d6 | |||
| 0667695390 | |||
| 4d322c1bf6 | |||
| a855ddacc7 | |||
| 9d48ef7322 | |||
| 2991f7acfb | |||
| 9dbfb565bb | |||
| f42e22b58f | |||
| 95f3315796 | |||
| 18ab57eb91 | |||
| 0dcbd8ccb8 | |||
| ed82b46f7b | |||
| dcc3044685 | |||
| 5cd62d7052 | |||
| 5e232d8c9f | |||
| 13793f8b3c | |||
| 55f875f95a | |||
| 14576be374 | |||
| 4cb2c26475 | |||
| b66a0b76ef | |||
| dd313b17b5 | |||
| bb01475e02 | |||
| 757959529b | |||
| 71ad1957b1 | |||
| 3ba5ea1d2d | |||
| 902da35f2b | |||
| aaa16a9527 | |||
| b98e95ee45 | |||
| 757160d6b7 | |||
| 4cf68eb018 | |||
| e91d5e5664 | |||
| b9e12aca3e | |||
| 53ca9475ee | |||
| e212842b50 | |||
| d2f7cba43d | |||
| 84558f79d6 | |||
| 178e59f287 | |||
| 7acccc0763 | |||
| 8ac21d2fd4 | |||
| 7873800f87 | |||
| 727041f020 | |||
| de0c62a37f | |||
| 68f420991e | |||
| f211ebeb0a | |||
| b91ef9f539 | |||
| ac0601630e | |||
| 3239491144 | |||
| c3b0a73507 | |||
| b218eb2c00 | |||
| 37d9f76fea | |||
| 7f2e12ba23 | |||
| 21b43287e3 | |||
| 357b48dc8f | |||
| 2cbd78b139 | |||
| ee5dd5842b | |||
| 074b8138de | |||
| cd0b9c0a96 | |||
| a09516944d | |||
| b82660823d | |||
| fde4402fbb | |||
| 5fe0776074 | |||
| d218f316d3 | |||
| f19e69948a | |||
| e6807d36b5 | |||
| 1ece230621 | |||
| d934d9d759 | |||
| bf7fabb20a | |||
| 88a2f317d8 | |||
| a30c4379a6 | |||
| b509c6631d | |||
| 5a725fa4b0 | |||
| dbe2143b7a | |||
| 7b687280d7 | |||
| 8db6c8bd4f | |||
| bf91dacb94 | |||
| 33e0892e95 | |||
| 42b146d5d0 | |||
| 2bebaf2cf8 | |||
| 6181328ac1 | |||
| c64d4b0914 | |||
| f94fd0d69d | |||
| 2cd66436bc | |||
| e2c5b9058b | |||
| cc751960ac | |||
| 433c73c9d3 | |||
| 680c2162a7 | |||
| a2b38ffb02 | |||
| 6395870c00 | |||
| 9562ba432f | |||
| d7d7d64b45 | |||
| e76bdbd62d | |||
| 067e869141 | |||
| 51224a69d9 | |||
| f56018d46a | |||
| b846185f8a | |||
| ff81e55839 | |||
| 1815d2b6d3 | |||
| 14cba76ba8 | |||
| 3d757c7814 | |||
| fd700f92ae | |||
| f41665f5a9 | |||
| 8b385c0b7b | |||
| 282f8db933 | |||
| 662b08c242 | |||
| b7e601be16 | |||
| 395676fcb1 | |||
| cc4df1c995 | |||
| 48eada2c37 |
@@ -1,6 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '8'
|
||||
- '10'
|
||||
services:
|
||||
- mongodb
|
||||
cache:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:8
|
||||
FROM node:10
|
||||
|
||||
ENV ADMIN_EMAIL admin@habitica.com
|
||||
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
|
||||
@@ -8,6 +8,7 @@ ENV BASE_URL https://habitica.com
|
||||
ENV FACEBOOK_KEY 128307497299777
|
||||
ENV GA_ID UA-33510635-1
|
||||
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
|
||||
ENV LOGGLY_CLIENT_TOKEN ab5663bf-241f-4d14-8783-7d80db77089a
|
||||
ENV NODE_ENV production
|
||||
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:8
|
||||
FROM node:10
|
||||
|
||||
# Install global packages
|
||||
RUN npm install -g gulp-cli mocha
|
||||
|
||||
@@ -1,115 +1,82 @@
|
||||
{
|
||||
"PORT":3000,
|
||||
"ENABLE_CONSOLE_LOGS_IN_PROD":"false",
|
||||
"IP":"0.0.0.0",
|
||||
"WEB_CONCURRENCY":1,
|
||||
"BASE_URL":"http://localhost:3000",
|
||||
"FACEBOOK_KEY":"123456789012345",
|
||||
"FACEBOOK_SECRET":"aaaabbbbccccddddeeeeffff00001111",
|
||||
"GOOGLE_CLIENT_ID":"123456789012345",
|
||||
"GOOGLE_CLIENT_SECRET":"aaaabbbbccccddddeeeeffff00001111",
|
||||
"PLAY_API": {
|
||||
"CLIENT_ID": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"CLIENT_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"ACCESS_TOKEN":"aaaabbbbccccddddeeeeffff00001111",
|
||||
"REFRESH_TOKEN":"aaaabbbbccccddddeeeeffff00001111"
|
||||
},
|
||||
"NODE_DB_URI":"mongodb://localhost/habitrpg",
|
||||
"TEST_DB_URI":"mongodb://localhost/habitrpg_test",
|
||||
"NODE_ENV":"development",
|
||||
"ENABLE_CONSOLE_LOGS_IN_TEST": "false",
|
||||
"CRON_SAFE_MODE":"false",
|
||||
"CRON_SEMI_SAFE_MODE":"false",
|
||||
"MAINTENANCE_MODE": "false",
|
||||
"SESSION_SECRET":"YOUR SECRET HERE",
|
||||
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
|
||||
"SESSION_SECRET_IV": "12345678912345678912345678912345",
|
||||
"ADMIN_EMAIL": "you@example.com",
|
||||
"SMTP_USER":"user@example.com",
|
||||
"SMTP_PASS":"password",
|
||||
"SMTP_SERVICE":"Gmail",
|
||||
"SMTP_HOST":"example.com",
|
||||
"SMTP_PORT": 587,
|
||||
"SMTP_TLS": true,
|
||||
"STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111",
|
||||
"STRIPE_PUB_KEY":"22223333444455556666777788889999",
|
||||
"NEW_RELIC_LICENSE_KEY":"NEW_RELIC_LICENSE_KEY",
|
||||
"NEW_RELIC_NO_CONFIG_FILE":"true",
|
||||
"NEW_RELIC_APPLICATION_ID":"NEW_RELIC_APPLICATION_ID",
|
||||
"NEW_RELIC_API_KEY":"NEW_RELIC_API_KEY",
|
||||
"GA_ID": "GA_ID",
|
||||
"AMPLITUDE_KEY": "AMPLITUDE_KEY",
|
||||
"AMPLITUDE_SECRET": "AMPLITUDE_SECRET",
|
||||
"AMAZON_PAYMENTS": {
|
||||
"SELLER_ID": "SELLER_ID",
|
||||
"CLIENT_ID": "CLIENT_ID",
|
||||
"MWS_KEY": "",
|
||||
"MWS_SECRET": ""
|
||||
},
|
||||
"FLAG_REPORT_EMAIL": "email@mod.com,email2@mod.com",
|
||||
"EMAIL_SERVER": {
|
||||
"url": "http://example.com",
|
||||
"authUser": "user",
|
||||
"authPassword": "password"
|
||||
},
|
||||
"S3":{
|
||||
"bucket":"bucket",
|
||||
"accessKeyId":"accessKeyId",
|
||||
"secretAccessKey":"secretAccessKey"
|
||||
},
|
||||
"SLACK_URL": "https://hooks.slack.com/services/some-url",
|
||||
"TRANSIFEX_SLACK_CHANNEL": "transifex",
|
||||
"PAYPAL":{
|
||||
"billing_plans": {
|
||||
"basic_earned":"basic_earned",
|
||||
"basic_3mo":"basic_3mo",
|
||||
"basic_6mo":"basic_6mo",
|
||||
"google_6mo":"google_6mo",
|
||||
"basic_12mo":"basic_12mo"
|
||||
},
|
||||
"mode":"sandbox",
|
||||
"client_id":"client_id",
|
||||
"client_secret":"client_secret",
|
||||
"experience_profile_id": ""
|
||||
},
|
||||
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
|
||||
"LOGGLY_TOKEN": "token",
|
||||
"LOGGLY_CLIENT_TOKEN": "token",
|
||||
"LOGGLY_ACCOUNT": "account",
|
||||
"PUSH_CONFIGS": {
|
||||
"GCM_SERVER_API_KEY": "",
|
||||
"APN_ENABLED": "false",
|
||||
"APN_KEY_ID": "xxxxxxxxxx",
|
||||
"APN_KEY": "xxxxxxxxxx",
|
||||
"APN_TEAM_ID": "aaabbbcccd",
|
||||
"FCM_SERVER_API_KEY": ""
|
||||
},
|
||||
"SITE_HTTP_AUTH": {
|
||||
"ENABLED": "false",
|
||||
"USERNAME": "admin",
|
||||
"PASSWORD": "password"
|
||||
},
|
||||
"SLACK": {
|
||||
"FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
|
||||
"FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
|
||||
"SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id"
|
||||
},
|
||||
"ITUNES_SHARED_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"EMAILS" : {
|
||||
"COMMUNITY_MANAGER_EMAIL" : "admin@habitica.com",
|
||||
"TECH_ASSISTANCE_EMAIL" : "admin@habitica.com",
|
||||
"PRESS_ENQUIRY_EMAIL" : "admin@habitica.com"
|
||||
},
|
||||
"LOGGLY" : {
|
||||
"TOKEN" : "example-token",
|
||||
"SUBDOMAIN" : "exmaple-subdomain"
|
||||
},
|
||||
"KAFKA": {
|
||||
"GROUP_ID": "",
|
||||
"CLOUDKARAFKA_BROKERS": "",
|
||||
"CLOUDKARAFKA_USERNAME": "",
|
||||
"CLOUDKARAFKA_PASSWORD": "",
|
||||
"CLOUDKARAFKA_TOPIC_PREFIX": ""
|
||||
},
|
||||
"MIGRATION_CONNECT_STRING": "mongodb://localhost:27017/habitrpg?auto_reconnect=true"
|
||||
"ADMIN_EMAIL": "you@example.com",
|
||||
"AMAZON_PAYMENTS_CLIENT_ID": "CLIENT_ID",
|
||||
"AMAZON_PAYMENTS_MODE": "sandbox",
|
||||
"AMAZON_PAYMENTS_MWS_KEY": "MWS_KEY",
|
||||
"AMAZON_PAYMENTS_MWS_SECRET": "MWS_SECRET",
|
||||
"AMAZON_PAYMENTS_SELLER_ID": "SELLER_ID",
|
||||
"AMPLITUDE_KEY": "AMPLITUDE_KEY",
|
||||
"AMPLITUDE_SECRET": "AMPLITUDE_SECRET",
|
||||
"BASE_URL": "http://localhost:3000",
|
||||
"CRON_SAFE_MODE": "false",
|
||||
"CRON_SEMI_SAFE_MODE": "false",
|
||||
"DISABLE_REQUEST_LOGGING": "true",
|
||||
"EMAILS_COMMUNITY_MANAGER_EMAIL": "admin@habitica.com",
|
||||
"EMAILS_PRESS_ENQUIRY_EMAIL": "admin@habitica.com",
|
||||
"EMAILS_TECH_ASSISTANCE_EMAIL": "admin@habitica.com",
|
||||
"EMAIL_SERVER_AUTH_PASSWORD": "password",
|
||||
"EMAIL_SERVER_AUTH_USER": "user",
|
||||
"EMAIL_SERVER_URL": "http://example.com",
|
||||
"ENABLE_CONSOLE_LOGS_IN_PROD": "false",
|
||||
"ENABLE_CONSOLE_LOGS_IN_TEST": "false",
|
||||
"FACEBOOK_KEY": "123456789012345",
|
||||
"FACEBOOK_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"FLAG_REPORT_EMAIL": "email@example.com, email2@example.com",
|
||||
"GA_ID": "GA_ID",
|
||||
"GOOGLE_CLIENT_ID": "123456789012345",
|
||||
"GOOGLE_CLIENT_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
|
||||
"IGNORE_REDIRECT": "true",
|
||||
"ITUNES_SHARED_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"LOGGLY_CLIENT_TOKEN": "token",
|
||||
"LOGGLY_SUBDOMAIN": "example-subdomain",
|
||||
"LOGGLY_TOKEN": "example-token",
|
||||
"MAINTENANCE_MODE": "false",
|
||||
"NODE_DB_URI": "mongodb://localhost/habitrpg",
|
||||
"NODE_ENV": "development",
|
||||
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
|
||||
"PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo",
|
||||
"PAYPAL_BILLING_PLANS_basic_3mo": "basic_3mo",
|
||||
"PAYPAL_BILLING_PLANS_basic_6mo": "basic_6mo",
|
||||
"PAYPAL_BILLING_PLANS_basic_earned": "basic_earned",
|
||||
"PAYPAL_BILLING_PLANS_google_6mo": "google_6mo",
|
||||
"PAYPAL_CLIENT_ID": "client_id",
|
||||
"PAYPAL_CLIENT_SECRET": "client_secret",
|
||||
"PAYPAL_EXPERIENCE_PROFILE_ID": "xp_profile_id",
|
||||
"PAYPAL_MODE": "sandbox",
|
||||
"PLAY_API_ACCESS_TOKEN": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"PLAY_API_CLIENT_ID": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"PLAY_API_CLIENT_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"PLAY_API_REFRESH_TOKEN": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"PORT": 3000,
|
||||
"PUSH_CONFIGS_APN_ENABLED": "false",
|
||||
"PUSH_CONFIGS_APN_KEY": "xxxxxxxxxx",
|
||||
"PUSH_CONFIGS_APN_KEY_ID": "xxxxxxxxxx",
|
||||
"PUSH_CONFIGS_APN_TEAM_ID": "aaabbbcccd",
|
||||
"PUSH_CONFIGS_FCM_SERVER_API_KEY": "aaabbbcccd",
|
||||
"S3_ACCESS_KEY_ID": "accessKeyId",
|
||||
"S3_BUCKET": "bucket",
|
||||
"S3_SECRET_ACCESS_KEY": "secretAccessKey",
|
||||
"SESSION_SECRET": "YOUR SECRET HERE",
|
||||
"SESSION_SECRET_IV": "12345678912345678912345678912345",
|
||||
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
|
||||
"SITE_HTTP_AUTH_ENABLED": "false",
|
||||
"SITE_HTTP_AUTH_PASSWORD": "password",
|
||||
"SITE_HTTP_AUTH_USERNAME": "admin",
|
||||
"SLACK_FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
|
||||
"SLACK_FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
|
||||
"SLACK_SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id",
|
||||
"SLACK_URL": "https://hooks.slack.com/services/some-url",
|
||||
"SMTP_HOST": "example.com",
|
||||
"SMTP_PASS": "password",
|
||||
"SMTP_PORT": 587,
|
||||
"SMTP_SERVICE": "Gmail",
|
||||
"SMTP_TLS": "true",
|
||||
"SMTP_USER": "user@example.com",
|
||||
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
|
||||
"TEST_DB_URI": "mongodb://localhost/habitrpg_test",
|
||||
"TRANSIFEX_SLACK_CHANNEL": "transifex",
|
||||
"WEB_CONCURRENCY": 1,
|
||||
"SKIP_SSL_CHECK_KEY": "key"
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ gulp.task('test:content:safe', gulp.series('test:prepare:build', (cb) => {
|
||||
|
||||
gulp.task('test:api:unit', (done) => {
|
||||
let runner = exec(
|
||||
testBin('node_modules/.bin/istanbul cover --dir coverage/api-unit node_modules/mocha/bin/_mocha -- test/api/unit --recursive --require ./test/helpers/start-server'),
|
||||
testBin('istanbul cover --dir coverage/api-unit node_modules/mocha/bin/_mocha -- test/api/unit --recursive --require ./test/helpers/start-server'),
|
||||
(err) => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
@@ -180,12 +180,12 @@ gulp.task('test:api:unit', (done) => {
|
||||
});
|
||||
|
||||
gulp.task('test:api:unit:watch', () => {
|
||||
return gulp.watch(['website/server/libs/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit', done => done()));
|
||||
return gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit', done => done()));
|
||||
});
|
||||
|
||||
gulp.task('test:api-v3:integration', (done) => {
|
||||
let runner = exec(
|
||||
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
|
||||
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},
|
||||
(err) => {
|
||||
if (err) {
|
||||
@@ -217,7 +217,7 @@ gulp.task('test:api-v3:integration:separate-server', (done) => {
|
||||
|
||||
gulp.task('test:api-v4:integration', (done) => {
|
||||
let runner = exec(
|
||||
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v4-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v4 --recursive --require ./test/helpers/start-server'),
|
||||
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},
|
||||
(err) => {
|
||||
if (err) {
|
||||
@@ -254,4 +254,4 @@ gulp.task('test:api-v3', gulp.series(
|
||||
'test:api:unit',
|
||||
'test:api-v3:integration',
|
||||
done => done()
|
||||
));
|
||||
));
|
||||
|
||||
@@ -78,7 +78,6 @@ async function updateUser (user) {
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2018-11-07')},
|
||||
};
|
||||
|
||||
const fields = {
|
||||
@@ -0,0 +1,110 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20181231_nye';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
import mongoose from 'mongoose';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {'flags.newStuff': true};
|
||||
let push;
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2018'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2018',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2017'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2017',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2016'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2016',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2015'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2015',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
|
||||
set['items.gear.owned.head_special_nye2014'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye2014',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
} else {
|
||||
set['items.gear.owned.head_special_nye'] = false;
|
||||
push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_nye',
|
||||
_id: uuid(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({_id: user._id}, {$set: set, $push: {pinnedItems: {$each: push}}}).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
|
||||
const migrationName = 'mystery-items-201808.js'; // Update per month
|
||||
const authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Award this month's mystery items to subscribers
|
||||
*/
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201810', 'head_mystery_201810'];
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||
|
||||
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||
let UserNotification = require('../../website/server/models/userNotification').model;
|
||||
|
||||
function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
let query = {
|
||||
migration: {$ne: migrationName},
|
||||
'purchased.plan.customerId': { $ne: null },
|
||||
$or: [
|
||||
{ 'purchased.plan.dateTerminated': { $gte: new Date() } },
|
||||
{ 'purchased.plan.dateTerminated': { $exists: false } },
|
||||
{ 'purchased.plan.dateTerminated': { $eq: null } },
|
||||
],
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
let progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
let userPromises = users.map(updateUser);
|
||||
let lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const addToSet = {
|
||||
'purchased.plan.mysteryItems': {
|
||||
$each: MYSTERY_ITEMS,
|
||||
},
|
||||
};
|
||||
const push = {
|
||||
notifications: (new UserNotification({
|
||||
type: 'NEW_MYSTERY_ITEMS',
|
||||
data: {
|
||||
MYSTERY_ITEMS,
|
||||
},
|
||||
})).toJSON(),
|
||||
};
|
||||
|
||||
dbUsers.update({_id: user._id}, {$addToSet: addToSet, $push: push});
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${ count } users processed\n`);
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting (code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) {
|
||||
msg = 'ERROR!';
|
||||
}
|
||||
if (msg) {
|
||||
if (code) {
|
||||
console.error(msg);
|
||||
} else {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
@@ -17,7 +17,7 @@ function setUpServer () {
|
||||
setUpServer();
|
||||
|
||||
// Replace this with your migration
|
||||
const processUsers = require('../scripts/gdpr-delete-users.js');
|
||||
const processUsers = require('./users/mystery-items.js');
|
||||
processUsers()
|
||||
.then(function success () {
|
||||
process.exit(0);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/* eslint-disable no-console */
|
||||
import { sendTxn } from '../../../website/server/libs/email';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
const EMAIL_SLUG = 'mandrill-email-slug'; // Set email template to send
|
||||
const MIGRATION_NAME = 'bulk-email';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
sendTxn(
|
||||
user,
|
||||
EMAIL_SLUG,
|
||||
[{name: 'BASE_URL', content: BASE_URL}] // Add variables from template
|
||||
);
|
||||
|
||||
return await User.update({_id: user._id}, {$set: {migration: MIGRATION_NAME}}).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'auth.timestamps.loggedin': {$gt: moment().subtract(2, 'weeks').toDate()}, // customize or remove to target different populations
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
auth: 1,
|
||||
preferences: 1,
|
||||
profile: 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
|
||||
}
|
||||
};
|
||||
@@ -1,70 +1,13 @@
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = 'mystery_items_201901';
|
||||
const MYSTERY_ITEMS = ['head_mystery_201901', 'body_mystery_201901'];
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
import { model as UserNotification } from '../../website/server/models/userNotification';
|
||||
|
||||
const migrationName = 'mystery-items-201808.js'; // Update per month
|
||||
const authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Award this month's mystery items to subscribers
|
||||
*/
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201810', 'head_mystery_201810'];
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||
|
||||
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||
let UserNotification = require('../../website/server/models/userNotification').model;
|
||||
|
||||
function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
let query = {
|
||||
migration: {$ne: migrationName},
|
||||
'purchased.plan.customerId': { $ne: null },
|
||||
$or: [
|
||||
{ 'purchased.plan.dateTerminated': { $gte: new Date() } },
|
||||
{ 'purchased.plan.dateTerminated': { $exists: false } },
|
||||
{ 'purchased.plan.dateTerminated': { $eq: null } },
|
||||
],
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
let progressCount = 1000;
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
let userPromises = users.map(updateUser);
|
||||
let lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const addToSet = {
|
||||
@@ -80,31 +23,49 @@ function updateUser (user) {
|
||||
},
|
||||
})).toJSON(),
|
||||
};
|
||||
const set = {
|
||||
migration: MIGRATION_NAME,
|
||||
};
|
||||
|
||||
dbUsers.update({_id: user._id}, {$addToSet: addToSet, $push: push});
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
||||
return await User.update({_id: user._id}, {$set: set, $push: push, $addToSet: addToSet}).exec();
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${ count } users processed\n`);
|
||||
return exiting(0);
|
||||
}
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
'purchased.plan.customerId': { $ne: null },
|
||||
$or: [
|
||||
{ 'purchased.plan.dateTerminated': { $gte: new Date() } },
|
||||
{ 'purchased.plan.dateTerminated': { $exists: false } },
|
||||
{ 'purchased.plan.dateTerminated': { $eq: null } },
|
||||
],
|
||||
};
|
||||
|
||||
function exiting (code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) {
|
||||
msg = 'ERROR!';
|
||||
}
|
||||
if (msg) {
|
||||
if (code) {
|
||||
console.error(msg);
|
||||
} else {
|
||||
console.log(msg);
|
||||
const fields = {
|
||||
_id: 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],
|
||||
};
|
||||
}
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20181203_take_this';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {};
|
||||
let push;
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
if (typeof user.items.gear.owned.back_special_takeThis !== 'undefined') {
|
||||
push = false;
|
||||
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
|
||||
set['items.gear.owned.back_special_takeThis'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.back_special_takeThis', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
|
||||
set['items.gear.owned.body_special_takeThis'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_takeThis', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
|
||||
set['items.gear.owned.head_special_takeThis'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_takeThis', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
|
||||
set['items.gear.owned.armor_special_takeThis'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_takeThis', _id: uuid()}};
|
||||
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
|
||||
set['items.gear.owned.weapon_special_takeThis'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.weapon_special_takeThis', _id: uuid()}};
|
||||
} else {
|
||||
set['items.gear.owned.shield_special_takeThis'] = false;
|
||||
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.shield_special_takeThis', _id: uuid()}};
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
if (push) {
|
||||
return await User.update({_id: user._id}, {$set: set, $push: push}).exec();
|
||||
} else {
|
||||
return await User.update({_id: user._id}, {$set: set}).exec();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () {
|
||||
let query = {
|
||||
migration: {$ne: MIGRATION_NAME},
|
||||
challenges: '00708425-d477-41a5-bf27-6270466e7976',
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.73.0",
|
||||
"version": "4.81.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
@@ -33,7 +33,7 @@
|
||||
"compression": "^1.7.2",
|
||||
"cookie-session": "^1.2.0",
|
||||
"coupon-code": "^0.4.5",
|
||||
"cross-env": "^5.1.5",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"csv-stringify": "^4.3.1",
|
||||
"cwait": "^1.1.1",
|
||||
@@ -80,17 +80,17 @@
|
||||
"ps-tree": "^1.0.0",
|
||||
"pug": "^2.0.3",
|
||||
"rimraf": "^2.4.3",
|
||||
"sass-loader": "^7.0.0",
|
||||
"sass-loader": "^7.0.3",
|
||||
"shelljs": "^0.8.2",
|
||||
"short-uuid": "^3.0.0",
|
||||
"smartbanner.js": "^1.9.1",
|
||||
"stripe": "^5.9.0",
|
||||
"superagent": "^3.8.3",
|
||||
"superagent": "^4.0.0",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"svgo": "^1.0.5",
|
||||
"svgo-loader": "^2.1.0",
|
||||
"universal-analytics": "^0.4.16",
|
||||
"universal-analytics": "^0.4.17",
|
||||
"update": "^0.7.4",
|
||||
"upgrade": "^1.1.0",
|
||||
"url-loader": "^1.0.0",
|
||||
@@ -107,15 +107,15 @@
|
||||
"vuedraggable": "^2.15.0",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||
"webpack": "^3.12.0",
|
||||
"webpack-merge": "^4.0.0",
|
||||
"winston": "^2.4.2",
|
||||
"webpack-merge": "^4.1.3",
|
||||
"winston": "^2.4.3",
|
||||
"winston-loggly-bulk": "^2.0.2",
|
||||
"xml2js": "^0.4.4"
|
||||
},
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "^8.9.4",
|
||||
"npm": "^5.6.0"
|
||||
"node": "^10",
|
||||
"npm": "^6"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.vue .",
|
||||
@@ -144,13 +144,13 @@
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/test-utils": "^1.0.0-beta.16",
|
||||
"@vue/test-utils": "^1.0.0-beta.19",
|
||||
"babel-plugin-istanbul": "^4.1.6",
|
||||
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chalk": "^2.4.1",
|
||||
"chromedriver": "^2.38.3",
|
||||
"chromedriver": "^2.40.0",
|
||||
"connect-history-api-fallback": "^1.1.0",
|
||||
"coveralls": "^3.0.1",
|
||||
"cross-spawn": "^6.0.5",
|
||||
@@ -164,7 +164,7 @@
|
||||
"expect.js": "^0.3.1",
|
||||
"http-proxy-middleware": "^0.19.0",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"karma": "^3.0.0",
|
||||
"karma": "^3.1.3",
|
||||
"karma-babel-preprocessor": "^7.0.0",
|
||||
"karma-chai-plugins": "^0.9.0",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
@@ -180,7 +180,7 @@
|
||||
"mocha": "^5.1.1",
|
||||
"monk": "^6.0.6",
|
||||
"nightwatch": "^0.9.21",
|
||||
"puppeteer": "^1.4.0",
|
||||
"puppeteer": "^1.5.0",
|
||||
"require-again": "^2.0.0",
|
||||
"selenium-server": "^3.12.0",
|
||||
"sinon": "^6.3.5",
|
||||
@@ -190,8 +190,5 @@
|
||||
"webpack-dev-middleware": "^2.0.5",
|
||||
"webpack-hot-middleware": "^2.22.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"memwatch-next": "^0.3.0",
|
||||
"node-rdkafka": "^2.3.0"
|
||||
}
|
||||
"optionalDependencies": {}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,13 @@ async function _deleteAmplitudeData (userId, email) {
|
||||
if (response) console.log(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
async function _deleteHabiticaData (user) {
|
||||
async function _deleteHabiticaData (user, email) {
|
||||
await User.update(
|
||||
{_id: user._id},
|
||||
{$set: {
|
||||
'auth.local.passwordHashMethod': 'bcrypt',
|
||||
'auth.local.email': email,
|
||||
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
|
||||
'auth.local.passwordHashMethod': 'bcrypt',
|
||||
}}
|
||||
);
|
||||
const response = await axios.delete(
|
||||
@@ -75,7 +76,7 @@ async function _processEmailAddress (email) {
|
||||
} else {
|
||||
for (const user of users) {
|
||||
await _deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop
|
||||
await _deleteHabiticaData(user); // eslint-disable-line no-await-in-loop
|
||||
await _deleteHabiticaData(user, email); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,28 +12,27 @@ const nconf = require('nconf');
|
||||
const _ = require('lodash');
|
||||
const paypal = require('paypal-rest-sdk');
|
||||
const blocks = require('../website/common').content.subscriptionBlocks;
|
||||
const live = nconf.get('PAYPAL:mode') === 'live';
|
||||
const live = nconf.get('PAYPAL_MODE') === 'live';
|
||||
|
||||
nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.json')));
|
||||
|
||||
let OP = 'create'; // list create update remove
|
||||
let OP = 'create'; // list get update create create-webprofile
|
||||
|
||||
paypal.configure({
|
||||
mode: nconf.get('PAYPAL:mode'), // sandbox or live
|
||||
client_id: nconf.get('PAYPAL:client_id'),
|
||||
client_secret: nconf.get('PAYPAL:client_secret'),
|
||||
mode: nconf.get('PAYPAL_MODE'), // sandbox or live
|
||||
client_id: nconf.get('PAYPAL_CLIENT_ID'),
|
||||
client_secret: nconf.get('PAYPAL_CLIENT_SECRET'),
|
||||
});
|
||||
|
||||
// https://developer.paypal.com/docs/api/#billing-plans-and-agreements
|
||||
let billingPlanTitle = 'Habitica Subscription';
|
||||
let billingPlanAttributes = {
|
||||
name: billingPlanTitle,
|
||||
description: billingPlanTitle,
|
||||
type: 'INFINITE',
|
||||
merchant_preferences: {
|
||||
auto_bill_amount: 'yes',
|
||||
cancel_url: live ? 'https://habitica.com' : 'http://localhost:3000',
|
||||
return_url: `${live ? 'https://habitica.com' : 'http://localhost:3000' }/paypal/subscribe/success`,
|
||||
return_url: `${live ? 'https://habitica.com' : 'http://localhost:3000'}/paypal/subscribe/success`,
|
||||
},
|
||||
payment_definitions: [{
|
||||
type: 'REGULAR',
|
||||
@@ -45,7 +44,7 @@ let billingPlanAttributes = {
|
||||
_.each(blocks, (block) => {
|
||||
block.definition = _.cloneDeep(billingPlanAttributes);
|
||||
_.merge(block.definition.payment_definitions[0], {
|
||||
name: `${billingPlanTitle } ($${block.price} every ${block.months} months, recurring)`,
|
||||
name: `${billingPlanTitle} ($${block.price} every ${block.months} months, recurring)`,
|
||||
frequency_interval: `${block.months}`,
|
||||
amount: {
|
||||
currency: 'USD',
|
||||
@@ -63,7 +62,7 @@ switch (OP) {
|
||||
});
|
||||
break;
|
||||
case 'get':
|
||||
paypal.billingPlan.get(nconf.get('PAYPAL:billing_plans:12'), (err, plan) => {
|
||||
paypal.billingPlan.get(nconf.get('PAYPAL_BILLING_PLANS_basic_12mo'), (err, plan) => {
|
||||
console.log({err, plan});
|
||||
});
|
||||
break;
|
||||
@@ -75,7 +74,7 @@ switch (OP) {
|
||||
cancel_url: 'https://habitica.com',
|
||||
},
|
||||
};
|
||||
paypal.billingPlan.update(nconf.get('PAYPAL:billing_plans:12'), updatePayload, (err, res) => {
|
||||
paypal.billingPlan.update(nconf.get('PAYPAL_BILLING_PLANS_basic_12mo'), updatePayload, (err, res) => {
|
||||
console.log({err, plan: res});
|
||||
});
|
||||
break;
|
||||
@@ -101,9 +100,6 @@ switch (OP) {
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
case 'remove': break;
|
||||
|
||||
case 'create-webprofile':
|
||||
let webexpinfo = {
|
||||
name: 'HabiticaProfile',
|
||||
@@ -116,4 +112,4 @@ switch (OP) {
|
||||
console.log(error, result);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import {
|
||||
BadRequest,
|
||||
InternalServerError,
|
||||
NotFound,
|
||||
NotificationNotFound,
|
||||
} from '../../../../website/server/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
|
||||
describe('Custom Errors', () => {
|
||||
describe('CustomError', () => {
|
||||
@@ -66,6 +68,23 @@ describe('Custom Errors', () => {
|
||||
|
||||
expect(notAuthorizedError.message).to.eql('Custom Error Message');
|
||||
});
|
||||
|
||||
describe('NotificationNotFound', () => {
|
||||
it('is an instance of NotFound', () => {
|
||||
const notificationNotFoundErr = new NotificationNotFound();
|
||||
expect(notificationNotFoundErr).to.be.an.instanceOf(NotFound);
|
||||
});
|
||||
|
||||
it('it returns an http code of 404', () => {
|
||||
const notificationNotFoundErr = new NotificationNotFound();
|
||||
expect(notificationNotFoundErr.httpCode).to.eql(404);
|
||||
});
|
||||
|
||||
it('returns a standard message', () => {
|
||||
const notificationNotFoundErr = new NotificationNotFound();
|
||||
expect(notificationNotFoundErr.message).to.eql(i18n.t('messageNotificationNotFound'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('BadRequest', () => {
|
||||
|
||||
@@ -245,7 +245,9 @@ describe('Password Utilities', () => {
|
||||
|
||||
it('returns false if the user has no local auth', async () => {
|
||||
let user = await generateUser({
|
||||
auth: 'not an object with valid fields',
|
||||
auth: {
|
||||
facebook: {},
|
||||
},
|
||||
});
|
||||
let res = await validatePasswordResetCodeAndFindUser(encrypt(JSON.stringify({
|
||||
userId: user._id,
|
||||
|
||||
@@ -6,6 +6,7 @@ import iap from '../../../../../website/server/libs/inAppPurchases';
|
||||
import {model as User} from '../../../../../website/server/models/user';
|
||||
import common from '../../../../../website/common';
|
||||
import moment from 'moment';
|
||||
import {mockFindById, restoreFindById} from '../../../../helpers/mongoose.helper';
|
||||
|
||||
const i18n = common.i18n;
|
||||
|
||||
@@ -49,7 +50,7 @@ describe('Apple Payments', () => {
|
||||
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
||||
.returns(false);
|
||||
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -61,7 +62,7 @@ describe('Apple Payments', () => {
|
||||
iapGetPurchaseDataStub.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData').returns([]);
|
||||
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -71,7 +72,7 @@ describe('Apple Payments', () => {
|
||||
|
||||
it('errors if the user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -89,7 +90,7 @@ describe('Apple Payments', () => {
|
||||
transactionId: token,
|
||||
}]);
|
||||
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -131,7 +132,7 @@ describe('Apple Payments', () => {
|
||||
}]);
|
||||
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await applePayments.verifyGemPurchase(user, receipt, headers);
|
||||
await applePayments.verifyGemPurchase({user, receipt, headers});
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
@@ -151,6 +152,38 @@ describe('Apple Payments', () => {
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('gifts gems', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
|
||||
mockFindById(receivingUser);
|
||||
|
||||
iapGetPurchaseDataStub.restore();
|
||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||
.returns([{productId: gemsCanPurchase[0].productId,
|
||||
transactionId: token,
|
||||
}]);
|
||||
|
||||
const gift = {uuid: receivingUser._id};
|
||||
await applePayments.verifyGemPurchase({user, gift, receipt, headers});
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user: receivingUser,
|
||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||
amount: gemsCanPurchase[0].amount,
|
||||
headers,
|
||||
});
|
||||
restoreFindById();
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscribe', () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import iap from '../../../../../website/server/libs/inAppPurchases';
|
||||
import {model as User} from '../../../../../website/server/models/user';
|
||||
import common from '../../../../../website/common';
|
||||
import moment from 'moment';
|
||||
import {mockFindById, restoreFindById} from '../../../../helpers/mongoose.helper';
|
||||
|
||||
const i18n = common.i18n;
|
||||
|
||||
@@ -44,7 +45,7 @@ describe('Google Payments', () => {
|
||||
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
||||
.returns(false);
|
||||
|
||||
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
|
||||
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -55,7 +56,7 @@ describe('Google Payments', () => {
|
||||
it('should throw an error if productId is invalid', async () => {
|
||||
receipt = `{"token": "${token}", "productId": "invalid"}`;
|
||||
|
||||
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
|
||||
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -66,7 +67,7 @@ describe('Google Payments', () => {
|
||||
it('should throw an error if user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(false);
|
||||
|
||||
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
|
||||
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
@@ -78,7 +79,7 @@ describe('Google Payments', () => {
|
||||
|
||||
it('purchases gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
|
||||
await googlePayments.verifyGemPurchase({user, receipt, signature, headers});
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
@@ -99,6 +100,34 @@ describe('Google Payments', () => {
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('gifts gems', async () => {
|
||||
const receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
|
||||
mockFindById(receivingUser);
|
||||
|
||||
const gift = {uuid: receivingUser._id};
|
||||
await googlePayments.verifyGemPurchase({user, gift, receipt, signature, headers});
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledOnce;
|
||||
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
|
||||
data: receipt,
|
||||
signature,
|
||||
});
|
||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user: receivingUser,
|
||||
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
|
||||
amount: 5.25,
|
||||
headers,
|
||||
});
|
||||
restoreFindById();
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscribe', () => {
|
||||
|
||||
@@ -15,6 +15,7 @@ describe('checkout', () => {
|
||||
|
||||
function getPaypalCreateOptions (description, amount) {
|
||||
return {
|
||||
experience_profile_id: 'xp_profile_id',
|
||||
intent: 'sale',
|
||||
payer: { payment_method: 'Paypal' },
|
||||
redirect_urls: {
|
||||
|
||||
@@ -110,7 +110,7 @@ describe('slack', () => {
|
||||
});
|
||||
|
||||
it('noops if no flagging url is provided', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('SLACK:FLAGGING_URL').returns('');
|
||||
sandbox.stub(nconf, 'get').withArgs('SLACK_FLAGGING_URL').returns('');
|
||||
sandbox.stub(logger, 'error');
|
||||
let reRequiredSlack = requireAgain('../../../../website/server/libs/slack');
|
||||
|
||||
|
||||
@@ -73,6 +73,56 @@ describe('redirects middleware', () => {
|
||||
|
||||
expect(res.redirect).to.have.not.been.called;
|
||||
});
|
||||
|
||||
it('does not redirect if passed skip ssl request param is passed with corrrect key', () => {
|
||||
let nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||
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.originalUrl = '/static/front';
|
||||
req.query.skipSSLCheck = 'test-key';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
attachRedirects.forceSSL(req, res, next);
|
||||
|
||||
expect(res.redirect).to.have.not.been.called;
|
||||
});
|
||||
|
||||
it('does redirect if skip ssl request param is passed with incorrrect key', () => {
|
||||
let nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||
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.originalUrl = '/static/front?skipSSLCheck=INVALID';
|
||||
req.query.skipSSLCheck = 'INVALID';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
attachRedirects.forceSSL(req, res, next);
|
||||
|
||||
expect(res.redirect).to.be.calledOnce;
|
||||
expect(res.redirect).to.be.calledWith('https://habitica.com/static/front?skipSSLCheck=INVALID');
|
||||
});
|
||||
|
||||
it('does redirect if skip ssl check key is not set', () => {
|
||||
let nconfStub = sandbox.stub(nconf, 'get');
|
||||
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
|
||||
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.originalUrl = '/static/front';
|
||||
req.query.skipSSLCheck = 'INVALID';
|
||||
|
||||
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
|
||||
attachRedirects.forceSSL(req, res, next);
|
||||
|
||||
expect(res.redirect).to.be.calledOnce;
|
||||
expect(res.redirect).to.be.calledWith('https://habitica.com/static/front');
|
||||
});
|
||||
});
|
||||
|
||||
context('forceHabitica', () => {
|
||||
|
||||
@@ -32,8 +32,19 @@ describe('Group Model', () => {
|
||||
privacy: 'private',
|
||||
});
|
||||
|
||||
let _progress = {
|
||||
up: 10,
|
||||
down: 8,
|
||||
collectedItems: 5,
|
||||
};
|
||||
|
||||
questLeader = new User({
|
||||
party: { _id: party._id },
|
||||
party: {
|
||||
_id: party._id,
|
||||
quest: {
|
||||
progress: _progress,
|
||||
},
|
||||
},
|
||||
profile: { name: 'Quest Leader' },
|
||||
items: {
|
||||
quests: {
|
||||
@@ -45,20 +56,40 @@ describe('Group Model', () => {
|
||||
party.leader = questLeader._id;
|
||||
|
||||
participatingMember = new User({
|
||||
party: { _id: party._id },
|
||||
party: {
|
||||
_id: party._id,
|
||||
quest: {
|
||||
progress: _progress,
|
||||
},
|
||||
},
|
||||
profile: { name: 'Participating Member' },
|
||||
});
|
||||
sleepingParticipatingMember = new User({
|
||||
party: { _id: party._id },
|
||||
party: {
|
||||
_id: party._id,
|
||||
quest: {
|
||||
progress: _progress,
|
||||
},
|
||||
},
|
||||
profile: { name: 'Sleeping Participating Member' },
|
||||
preferences: { sleep: true },
|
||||
});
|
||||
nonParticipatingMember = new User({
|
||||
party: { _id: party._id },
|
||||
party: {
|
||||
_id: party._id,
|
||||
quest: {
|
||||
progress: _progress,
|
||||
},
|
||||
},
|
||||
profile: { name: 'Non-Participating Member' },
|
||||
});
|
||||
undecidedMember = new User({
|
||||
party: { _id: party._id },
|
||||
party: {
|
||||
_id: party._id,
|
||||
quest: {
|
||||
progress: _progress,
|
||||
},
|
||||
},
|
||||
profile: { name: 'Undecided Member' },
|
||||
});
|
||||
|
||||
@@ -1163,16 +1194,17 @@ describe('Group Model', () => {
|
||||
expect(party.quest.members).to.eql(expectedQuestMembers);
|
||||
});
|
||||
|
||||
it('applies updates to user object directly if user is participating', async () => {
|
||||
it('applies updates to user object directly if user is participating (without resetting progress, except progress.down)', async () => {
|
||||
await party.startQuest(participatingMember);
|
||||
|
||||
expect(participatingMember.party.quest.key).to.eql('whale');
|
||||
expect(participatingMember.party.quest.progress.up).to.eql(10);
|
||||
expect(participatingMember.party.quest.progress.down).to.eql(0);
|
||||
expect(participatingMember.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(participatingMember.party.quest.completed).to.eql(null);
|
||||
});
|
||||
|
||||
it('applies updates to other participating members', async () => {
|
||||
it('applies updates to other participating members (without resetting progress, except progress.down)', async () => {
|
||||
await party.startQuest(nonParticipatingMember);
|
||||
|
||||
questLeader = await User.findById(questLeader._id);
|
||||
@@ -1180,18 +1212,21 @@ describe('Group Model', () => {
|
||||
sleepingParticipatingMember = await User.findById(sleepingParticipatingMember._id);
|
||||
|
||||
expect(participatingMember.party.quest.key).to.eql('whale');
|
||||
expect(participatingMember.party.quest.progress.up).to.eql(10);
|
||||
expect(participatingMember.party.quest.progress.down).to.eql(0);
|
||||
expect(participatingMember.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(participatingMember.party.quest.completed).to.eql(null);
|
||||
|
||||
expect(sleepingParticipatingMember.party.quest.key).to.eql('whale');
|
||||
expect(sleepingParticipatingMember.party.quest.progress.up).to.eql(10);
|
||||
expect(sleepingParticipatingMember.party.quest.progress.down).to.eql(0);
|
||||
expect(sleepingParticipatingMember.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(sleepingParticipatingMember.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(sleepingParticipatingMember.party.quest.completed).to.eql(null);
|
||||
|
||||
expect(questLeader.party.quest.key).to.eql('whale');
|
||||
expect(questLeader.party.quest.progress.up).to.eql(10);
|
||||
expect(questLeader.party.quest.progress.down).to.eql(0);
|
||||
expect(questLeader.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(questLeader.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(questLeader.party.quest.completed).to.eql(null);
|
||||
});
|
||||
|
||||
@@ -1202,6 +1237,9 @@ describe('Group Model', () => {
|
||||
undecidedMember = await User.findById(undecidedMember._id);
|
||||
|
||||
expect(nonParticipatingMember.party.quest.key).to.not.eql('whale');
|
||||
expect(nonParticipatingMember.party.quest.progress.up).to.eql(10);
|
||||
expect(nonParticipatingMember.party.quest.progress.down).to.eql(8);
|
||||
expect(nonParticipatingMember.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(undecidedMember.party.quest.key).to.not.eql('whale');
|
||||
});
|
||||
|
||||
@@ -1369,8 +1407,9 @@ describe('Group Model', () => {
|
||||
let userQuest = participatingMember.party.quest;
|
||||
|
||||
expect(userQuest.key).to.eql('whale');
|
||||
expect(userQuest.progress.up).to.eql(10);
|
||||
expect(userQuest.progress.down).to.eql(0);
|
||||
expect(userQuest.progress.collectedItems).to.eql(0);
|
||||
expect(userQuest.progress.collectedItems).to.eql(5);
|
||||
expect(userQuest.completed).to.eql(null);
|
||||
});
|
||||
|
||||
@@ -1670,16 +1709,23 @@ describe('Group Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sets user quest object to a clean state', async () => {
|
||||
it('updates participating members quest object to a clean state (except for progress)', async () => {
|
||||
await party.finishQuest(quest);
|
||||
|
||||
let updatedLeader = await User.findById(questLeader._id);
|
||||
questLeader = await User.findById(questLeader._id);
|
||||
participatingMember = await User.findById(participatingMember._id);
|
||||
|
||||
expect(updatedLeader.party.quest.completed).to.eql('whale');
|
||||
expect(updatedLeader.party.quest.progress.up).to.eql(0);
|
||||
expect(updatedLeader.party.quest.progress.down).to.eql(0);
|
||||
expect(updatedLeader.party.quest.progress.collectedItems).to.eql(0);
|
||||
expect(updatedLeader.party.quest.RSVPNeeded).to.eql(false);
|
||||
expect(questLeader.party.quest.completed).to.eql('whale');
|
||||
expect(questLeader.party.quest.progress.up).to.eql(10);
|
||||
expect(questLeader.party.quest.progress.down).to.eql(8);
|
||||
expect(questLeader.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(questLeader.party.quest.RSVPNeeded).to.eql(false);
|
||||
|
||||
expect(participatingMember.party.quest.completed).to.eql('whale');
|
||||
expect(participatingMember.party.quest.progress.up).to.eql(10);
|
||||
expect(participatingMember.party.quest.progress.down).to.eql(8);
|
||||
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
|
||||
expect(participatingMember.party.quest.RSVPNeeded).to.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1843,6 +1889,62 @@ describe('Group Model', () => {
|
||||
expect(options.chat).to.eql(chat);
|
||||
});
|
||||
|
||||
it('sends webhooks for users with webhooks triggered by system messages', async () => {
|
||||
let guild = new Group({
|
||||
name: 'some guild',
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
let memberWithWebhook = new User({
|
||||
guilds: [guild._id],
|
||||
webhooks: [{
|
||||
type: 'groupChatReceived',
|
||||
url: 'http://someurl.com',
|
||||
options: {
|
||||
groupId: guild._id,
|
||||
},
|
||||
}],
|
||||
});
|
||||
let memberWithoutWebhook = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
let nonMemberWithWebhooks = new User({
|
||||
webhooks: [{
|
||||
type: 'groupChatReceived',
|
||||
url: 'http://a-different-url.com',
|
||||
options: {
|
||||
groupId: generateUUID(),
|
||||
},
|
||||
}],
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
memberWithWebhook.save(),
|
||||
memberWithoutWebhook.save(),
|
||||
nonMemberWithWebhooks.save(),
|
||||
]);
|
||||
|
||||
guild.leader = memberWithWebhook._id;
|
||||
|
||||
await guild.save();
|
||||
|
||||
const groupMessage = guild.sendChat('Test message.');
|
||||
await groupMessage.save();
|
||||
|
||||
await sleep();
|
||||
|
||||
expect(groupChatReceivedWebhook.send).to.be.calledOnce;
|
||||
|
||||
let args = groupChatReceivedWebhook.send.args[0];
|
||||
let webhooks = args[0].webhooks;
|
||||
let options = args[1];
|
||||
|
||||
expect(webhooks).to.have.a.lengthOf(1);
|
||||
expect(webhooks[0].id).to.eql(memberWithWebhook.webhooks[0].id);
|
||||
expect(options.group).to.eql(guild);
|
||||
expect(options.chat).to.eql(groupMessage);
|
||||
});
|
||||
|
||||
it('sends webhooks for each user with webhooks in group', async () => {
|
||||
let guild = new Group({
|
||||
name: 'some guild',
|
||||
|
||||
@@ -106,7 +106,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('messageCannotFlagSystemMessages', {communityManagerEmail: config.EMAILS.COMMUNITY_MANAGER_EMAIL}),
|
||||
message: t('messageCannotFlagSystemMessages', {communityManagerEmail: config.EMAILS_COMMUNITY_MANAGER_EMAIL}),
|
||||
});
|
||||
// let messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
// expect(messages[0].id).to.eql(skillMsg.id);
|
||||
|
||||
@@ -333,7 +333,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('inviteLimitReached', {techAssistanceEmail: nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL')}),
|
||||
message: t('inviteLimitReached', {techAssistanceEmail: nconf.get('EMAILS_TECH_ASSISTANCE_EMAIL')}),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('POST /notifications/:notificationId/read', () => {
|
||||
|
||||
await expect(user.post(`/notifications/${dummyId}/read`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
error: 'NotificationNotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('POST /notifications/:notificationId/see', () => {
|
||||
|
||||
await expect(user.post(`/notifications/${dummyId}/see`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
error: 'NotificationNotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('POST /notifications/read', () => {
|
||||
notificationIds: [dummyId],
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
error: 'NotificationNotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('POST /notifications/see', () => {
|
||||
notificationIds: [dummyId],
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
error: 'NotificationNotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
|
||||
import applePayments from '../../../../../../website/server/libs/payments/apple';
|
||||
|
||||
describe('payments : apple #norenewsubscribe', () => {
|
||||
let endpoint = '/iap/ios/norenew-subscribe';
|
||||
let sku = 'com.habitrpg.ios.habitica.subscription.3month';
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('verifies sub key', async () => {
|
||||
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies receipt existence', async () => {
|
||||
await expect(user.post(endpoint, {
|
||||
sku,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingReceipt'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let subscribeStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
subscribeStub = sinon.stub(applePayments, 'noRenewSubscribe').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
applePayments.noRenewSubscribe.restore();
|
||||
});
|
||||
|
||||
it('makes a purchase', async () => {
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
await user.post(endpoint, {
|
||||
sku,
|
||||
transaction: {receipt: 'receipt'},
|
||||
gift: {
|
||||
uuid: '1',
|
||||
},
|
||||
});
|
||||
|
||||
expect(subscribeStub).to.be.calledOnce;
|
||||
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][0].sku).to.eql(sku);
|
||||
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import {generateUser} from '../../../../../helpers/api-integration/v3';
|
||||
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
|
||||
import applePayments from '../../../../../../website/server/libs/payments/apple';
|
||||
|
||||
describe('payments : apple #verify', () => {
|
||||
@@ -9,6 +9,14 @@ describe('payments : apple #verify', () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('verifies receipt existence', async () => {
|
||||
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingReceipt'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let verifyStub;
|
||||
|
||||
@@ -31,10 +39,31 @@ describe('payments : apple #verify', () => {
|
||||
}});
|
||||
|
||||
expect(verifyStub).to.be.calledOnce;
|
||||
expect(verifyStub.args[0][0]._id).to.eql(user._id);
|
||||
expect(verifyStub.args[0][1]).to.eql('receipt');
|
||||
expect(verifyStub.args[0][2]['x-api-key']).to.eql(user.apiToken);
|
||||
expect(verifyStub.args[0][2]['x-api-user']).to.eql(user._id);
|
||||
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
|
||||
it('gifts a purchase', async () => {
|
||||
user = await generateUser({
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
await user.post(endpoint, {
|
||||
transaction: {
|
||||
receipt: 'receipt',
|
||||
},
|
||||
gift: {
|
||||
uuid: '1',
|
||||
}});
|
||||
|
||||
expect(verifyStub).to.be.calledOnce;
|
||||
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(verifyStub.args[0][0].gift.uuid).to.eql('1');
|
||||
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
|
||||
import googlePayments from '../../../../../../website/server/libs/payments/google';
|
||||
|
||||
describe('payments : google #norenewsubscribe', () => {
|
||||
let endpoint = '/iap/android/norenew-subscribe';
|
||||
let sku = 'com.habitrpg.android.habitica.subscription.3month';
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('verifies sub key', async () => {
|
||||
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingSubscriptionCode'),
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies receipt existence', async () => {
|
||||
await expect(user.post(endpoint, {
|
||||
sku,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingReceipt'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let subscribeStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
subscribeStub = sinon.stub(googlePayments, 'noRenewSubscribe').resolves({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
googlePayments.noRenewSubscribe.restore();
|
||||
});
|
||||
|
||||
it('makes a purchase', async () => {
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
await user.post(endpoint, {
|
||||
sku,
|
||||
transaction: {
|
||||
receipt: 'receipt',
|
||||
signature: 'signature',
|
||||
},
|
||||
});
|
||||
|
||||
expect(subscribeStub).to.be.calledOnce;
|
||||
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][0].sku).to.eql(sku);
|
||||
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(subscribeStub.args[0][0].signature).to.eql('signature');
|
||||
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
|
||||
it('gifts a purchase', async () => {
|
||||
user = await generateUser({
|
||||
'profile.name': 'sender',
|
||||
'purchased.plan.customerId': 'customer-id',
|
||||
'purchased.plan.planId': 'basic_3mo',
|
||||
'purchased.plan.lastBillingDate': new Date(),
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
await user.post(endpoint, {
|
||||
sku,
|
||||
transaction: {
|
||||
receipt: 'receipt',
|
||||
signature: 'signature',
|
||||
},
|
||||
gift: {
|
||||
uuid: '1',
|
||||
},
|
||||
});
|
||||
|
||||
expect(subscribeStub).to.be.calledOnce;
|
||||
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(subscribeStub.args[0][0].sku).to.eql(sku);
|
||||
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(subscribeStub.args[0][0].signature).to.eql('signature');
|
||||
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import {generateUser} from '../../../../../helpers/api-integration/v3';
|
||||
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
|
||||
import googlePayments from '../../../../../../website/server/libs/payments/google';
|
||||
|
||||
describe('payments : google #verify', () => {
|
||||
@@ -9,6 +9,14 @@ describe('payments : google #verify', () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('verifies receipt existence', async () => {
|
||||
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingReceipt'),
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
let verifyStub;
|
||||
|
||||
@@ -30,11 +38,30 @@ describe('payments : google #verify', () => {
|
||||
});
|
||||
|
||||
expect(verifyStub).to.be.calledOnce;
|
||||
expect(verifyStub.args[0][0]._id).to.eql(user._id);
|
||||
expect(verifyStub.args[0][1]).to.eql('receipt');
|
||||
expect(verifyStub.args[0][2]).to.eql('signature');
|
||||
expect(verifyStub.args[0][3]['x-api-key']).to.eql(user.apiToken);
|
||||
expect(verifyStub.args[0][3]['x-api-user']).to.eql(user._id);
|
||||
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(verifyStub.args[0][0].signature).to.eql('signature');
|
||||
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
|
||||
it('gifts a purchase', async () => {
|
||||
user = await generateUser({
|
||||
balance: 2,
|
||||
});
|
||||
|
||||
await user.post(endpoint, {
|
||||
transaction: {receipt: 'receipt', signature: 'signature'},
|
||||
gift: {uuid: '1'},
|
||||
});
|
||||
|
||||
expect(verifyStub).to.be.calledOnce;
|
||||
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
|
||||
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
|
||||
expect(verifyStub.args[0][0].signature).to.eql('signature');
|
||||
expect(verifyStub.args[0][0].gift.uuid).to.eql('1');
|
||||
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
|
||||
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,12 @@ describe('GET /user/anonymized', () => {
|
||||
'profile.name': 'profile',
|
||||
'purchased.plan': 'purchased plan',
|
||||
contributor: 'contributor',
|
||||
invitations: 'invitations',
|
||||
invitations: {
|
||||
guilds: ['guild1', 'guild2'],
|
||||
party: {
|
||||
_id: 'partyid',
|
||||
},
|
||||
},
|
||||
'items.special.nyeReceived': 'some',
|
||||
'items.special.valentineReceived': 'some',
|
||||
webhooks: [{url: 'https://somurl.com'}],
|
||||
|
||||
@@ -94,9 +94,6 @@ describe('POST /user/auth/reset-password-set-new-one', () => {
|
||||
userId: user._id,
|
||||
expiresAt: moment().add({days: 1}),
|
||||
}));
|
||||
await user.update({
|
||||
auth: 'not an object with valid fields',
|
||||
});
|
||||
|
||||
await expect(api.post(`${endpoint}`, {
|
||||
code,
|
||||
|
||||
@@ -45,7 +45,7 @@ describe('POST /user/auth/local/login', () => {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS:COMMUNITY_MANAGER_EMAIL'), userId: user._id }),
|
||||
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL'), userId: user._id }),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ describe('PUT /user/auth/update-email', () => {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotFulfillReq', { techAssistanceEmail: nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL') }),
|
||||
message: t('cannotFulfillReq', { techAssistanceEmail: nconf.get('EMAILS_TECH_ASSISTANCE_EMAIL') }),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
import Avatar from 'client/components/avatar';
|
||||
import Vue from 'vue';
|
||||
import generateStore from 'client/store';
|
||||
|
||||
context('avatar.vue', () => {
|
||||
let Constructr;
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
Constructr = Vue.extend(Avatar);
|
||||
|
||||
vm = new Constructr({
|
||||
propsData: {
|
||||
member: {
|
||||
stats: {
|
||||
buffs: {},
|
||||
},
|
||||
preferences: {
|
||||
hair: {},
|
||||
},
|
||||
items: {
|
||||
gear: {
|
||||
equipped: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
vm.$store = generateStore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('hasClass', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
stats: { lvl: 17 },
|
||||
preferences: { disableClasses: true },
|
||||
flags: { classSelected: false },
|
||||
};
|
||||
});
|
||||
|
||||
it('accurately reports class status', () => {
|
||||
expect(vm.hasClass).to.equal(false);
|
||||
|
||||
vm.member.preferences.disableClasses = false;
|
||||
vm.member.flags.classSelected = true;
|
||||
|
||||
expect(vm.hasClass).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isBuffed', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
stats: {
|
||||
buffs: {},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('accurately reports if buffed', () => {
|
||||
expect(vm.isBuffed).to.equal(undefined);
|
||||
|
||||
vm.member.stats.buffs = { str: 1 };
|
||||
|
||||
expect(vm.isBuffed).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('paddingTop', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
items: {},
|
||||
};
|
||||
});
|
||||
|
||||
it('defaults to 28px', () => {
|
||||
vm.avatarOnly = true;
|
||||
expect(vm.paddingTop).to.equal('28px');
|
||||
});
|
||||
|
||||
it('is 24.5px if user has a pet', () => {
|
||||
vm.member.items = {
|
||||
currentPet: { name: 'Foo' },
|
||||
};
|
||||
|
||||
expect(vm.paddingTop).to.equal('24.5px');
|
||||
});
|
||||
|
||||
it('is 0px if user has a mount', () => {
|
||||
vm.member.items = {
|
||||
currentMount: { name: 'Bar' },
|
||||
};
|
||||
|
||||
expect(vm.paddingTop).to.equal('0px');
|
||||
});
|
||||
|
||||
it('can be overriden', () => {
|
||||
vm.overrideTopPadding = '27px';
|
||||
expect(vm.paddingTop).to.equal('27px');
|
||||
});
|
||||
});
|
||||
|
||||
describe('costumeClass', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
preferences: {},
|
||||
};
|
||||
});
|
||||
|
||||
it('returns if showing equiped gear', () => {
|
||||
expect(vm.costumeClass).to.equal('equipped');
|
||||
});
|
||||
it('returns if wearing a costume', () => {
|
||||
vm.member.preferences = { costume: true };
|
||||
expect(vm.costumeClass).to.equal('costume');
|
||||
});
|
||||
});
|
||||
|
||||
describe('visualBuffs', () => {
|
||||
it('returns an array of buffs', () => {
|
||||
vm.member = {
|
||||
stats: {
|
||||
class: 'Warrior',
|
||||
},
|
||||
};
|
||||
|
||||
expect(vm.visualBuffs).to.include({snowball: 'snowman'});
|
||||
expect(vm.visualBuffs).to.include({spookySparkles: 'ghost'});
|
||||
expect(vm.visualBuffs).to.include({shinySeed: 'avatar_floral_Warrior'});
|
||||
expect(vm.visualBuffs).to.include({seafoam: 'seafoam_star'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('backgroundClass', () => {
|
||||
beforeEach(() => {
|
||||
vm.member.preferences = { background: 'pony' };
|
||||
});
|
||||
|
||||
it('shows the background', () => {
|
||||
expect(vm.backgroundClass).to.equal('background_pony');
|
||||
});
|
||||
|
||||
it('can be overridden', () => {
|
||||
vm.overrideAvatarGear = { background: 'character' };
|
||||
|
||||
expect(vm.backgroundClass).to.equal('background_character');
|
||||
});
|
||||
|
||||
it('returns to a blank string if not showing background', () => {
|
||||
vm.withBackground = false;
|
||||
vm.avatarOnly = true;
|
||||
|
||||
expect(vm.backgroundClass).to.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('specialMountClass', () => {
|
||||
it('checks if riding a Kangaroo', () => {
|
||||
vm.member = {
|
||||
stats: {
|
||||
class: 'None',
|
||||
},
|
||||
items: {},
|
||||
};
|
||||
|
||||
expect(vm.specialMountClass).to.equal(undefined);
|
||||
|
||||
vm.member.items = {
|
||||
currentMount: ['Kangaroo'],
|
||||
};
|
||||
|
||||
expect(vm.specialMountClass).to.equal('offset-kangaroo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('skinClass', () => {
|
||||
it('returns current skin color', () => {
|
||||
vm.member = {
|
||||
stats: {},
|
||||
preferences: {
|
||||
skin: 'blue',
|
||||
},
|
||||
};
|
||||
|
||||
expect(vm.skinClass).to.equal('skin_blue');
|
||||
});
|
||||
|
||||
it('returns if sleep or not', () => {
|
||||
vm.member = {
|
||||
stats: {},
|
||||
preferences: {
|
||||
skin: 'blue',
|
||||
sleep: false,
|
||||
},
|
||||
};
|
||||
|
||||
expect(vm.skinClass).to.equal('skin_blue');
|
||||
|
||||
vm.member.preferences.sleep = true;
|
||||
|
||||
expect(vm.skinClass).to.equal('skin_blue_sleep');
|
||||
});
|
||||
});
|
||||
|
||||
context('methods', () => {
|
||||
describe('getGearClass', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
items: {
|
||||
gear: {
|
||||
equipped: { Hat: 'Fancy Tophat' },
|
||||
},
|
||||
},
|
||||
preferences: { costume: false },
|
||||
};
|
||||
});
|
||||
|
||||
it('returns undefined if no match', () => {
|
||||
expect(vm.getGearClass('foo')).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('returns the matching gear', () => {
|
||||
expect(vm.getGearClass('Hat')).to.equal('Fancy Tophat');
|
||||
});
|
||||
|
||||
it('can be overridden', () => {
|
||||
vm.overrideAvatarGear = { Hat: 'Dapper Bowler' };
|
||||
|
||||
expect(vm.getGearClass('Hat')).to.equal('Dapper Bowler');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hideGear', () => {
|
||||
it('returns no weapon equipped', () => {
|
||||
vm.member.items.gear.equipped = {};
|
||||
expect(vm.hideGear('weapon')).to.equal(false);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
items: {
|
||||
gear: {
|
||||
equipped: {
|
||||
weapon: {
|
||||
baseWeapon: 'Spoon',
|
||||
twoHanded: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
preferences: { costume: false },
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
describe('show avatar', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
stats: {
|
||||
buffs: {
|
||||
snowball: false,
|
||||
seafoam: false,
|
||||
spookySparkles: false,
|
||||
shinySeed: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
it('does if not showing visual buffs', () => {
|
||||
expect(vm.showAvatar()).to.equal(true);
|
||||
|
||||
let buffs = vm.member.stats.buffs;
|
||||
|
||||
buffs.snowball = true;
|
||||
expect(vm.showAvatar()).to.equal(false);
|
||||
|
||||
buffs.snowball = false;
|
||||
buffs.spookySparkles = true;
|
||||
expect(vm.showAvatar()).to.equal(false);
|
||||
|
||||
buffs.spookySparkles = false;
|
||||
buffs.shinySeed = true;
|
||||
expect(vm.showAvatar()).to.equal(false);
|
||||
|
||||
buffs.shinySeed = false;
|
||||
buffs.seafoam = true;
|
||||
expect(vm.showAvatar()).to.equal(false);
|
||||
|
||||
buffs.seafoam = false;
|
||||
vm.showVisualBuffs = false;
|
||||
expect(vm.showAvatar()).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { data, gems, buffs, preferences, tasksOrder } from 'client/store/getters/user';
|
||||
|
||||
context('user getters', () => {
|
||||
describe('data', () => {
|
||||
it('returns the user\'s data', () => {
|
||||
expect(data({
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
lvl: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}).lvl).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gems', () => {
|
||||
it('returns the user\'s gems', () => {
|
||||
expect(gems({
|
||||
state: {
|
||||
user: {
|
||||
data: { balance: 4.5 },
|
||||
},
|
||||
},
|
||||
})).to.equal(18);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buffs', () => {
|
||||
it('returns the user\'s buffs', () => {
|
||||
expect(buffs({
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
stats: {
|
||||
buffs: [1],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(0)).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('preferences', () => {
|
||||
it('returns the user\'s preferences', () => {
|
||||
expect(preferences({
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
preferences: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tasksOrder', () => {
|
||||
it('returns the user\'s tasksOrder', () => {
|
||||
expect(tasksOrder({
|
||||
state: {
|
||||
user: {
|
||||
tasksOrder: {
|
||||
masters: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})('master')).to.equal(1);
|
||||
|
||||
expect(tasksOrder()).to.not.equal('null');
|
||||
expect(tasksOrder()).to.not.equal('undefined');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import { gems as userGems } from 'client/store/getters/user';
|
||||
|
||||
describe('userGems getter', () => {
|
||||
it('returns the user\'s gems', () => {
|
||||
expect(userGems({
|
||||
state: {
|
||||
user: {
|
||||
data: {balance: 4.5},
|
||||
},
|
||||
},
|
||||
})).to.equal(18);
|
||||
});
|
||||
});
|
||||
@@ -246,5 +246,14 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
|
||||
expect(user.items.gear.owned).to.have.property('head_special_2', true);
|
||||
});
|
||||
|
||||
it('does buyGear equipment if it is an armoire item that an user previously lost', () => {
|
||||
user.stats.gp = 200;
|
||||
user.items.gear.owned.shield_armoire_ramHornShield = false;
|
||||
|
||||
buyGear(user, {params: {key: 'shield_armoire_ramHornShield'}});
|
||||
|
||||
expect(user.items.gear.owned).to.have.property('shield_armoire_ramHornShield', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
export async function mockFindById (response) {
|
||||
const mockFind = {
|
||||
select () {
|
||||
return this;
|
||||
},
|
||||
lean () {
|
||||
return this;
|
||||
},
|
||||
exec () {
|
||||
return Promise.resolve(response);
|
||||
},
|
||||
};
|
||||
sinon.stub(mongoose.Model, 'findById').returns(mockFind);
|
||||
}
|
||||
|
||||
export function restoreFindById () {
|
||||
return mongoose.Model.findById.restore();
|
||||
}
|
||||
@@ -15,10 +15,11 @@ setupNconf(configFile);
|
||||
// @TODO: Do we need? const CLIENT_VARS = ['language', 'isStaticPage', 'availableLanguages', 'translations',
|
||||
// 'FACEBOOK_KEY', 'GOOGLE_CLIENT_ID', 'NODE_ENV', 'BASE_URL', 'GA_ID',
|
||||
// 'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY',
|
||||
// 'worldDmg', 'mods', 'IS_MOBILE', 'PUSHER:KEY', 'PUSHER:ENABLED'];
|
||||
// 'worldDmg', 'mods', 'IS_MOBILE'];
|
||||
|
||||
const AMAZON_SELLER_ID = nconf.get('AMAZON_PAYMENTS:SELLER_ID') || nconf.get('AMAZON_PAYMENTS_SELLER_ID');
|
||||
const AMAZON_CLIENT_ID = nconf.get('AMAZON_PAYMENTS:CLIENT_ID') || nconf.get('AMAZON_PAYMENTS_CLIENT_ID');
|
||||
const AMAZON_SELLER_ID = nconf.get('AMAZON_PAYMENTS_SELLER_ID');
|
||||
const AMAZON_CLIENT_ID = nconf.get('AMAZON_PAYMENTS_CLIENT_ID');
|
||||
const AMAZON_MODE = nconf.get('AMAZON_PAYMENTS_MODE');
|
||||
|
||||
let env = {
|
||||
NODE_ENV: '"production"',
|
||||
@@ -26,15 +27,16 @@ let env = {
|
||||
AMAZON_PAYMENTS: {
|
||||
SELLER_ID: `"${AMAZON_SELLER_ID}"`,
|
||||
CLIENT_ID: `"${AMAZON_CLIENT_ID}"`,
|
||||
MODE: `"${AMAZON_MODE}"`,
|
||||
},
|
||||
EMAILS: {
|
||||
COMMUNITY_MANAGER_EMAIL: `"${nconf.get('EMAILS:COMMUNITY_MANAGER_EMAIL')}"`,
|
||||
TECH_ASSISTANCE_EMAIL: `"${nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL')}"`,
|
||||
PRESS_ENQUIRY_EMAIL: `"${nconf.get('EMAILS:PRESS_ENQUIRY_EMAIL')}"`,
|
||||
COMMUNITY_MANAGER_EMAIL: `"${nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL')}"`,
|
||||
TECH_ASSISTANCE_EMAIL: `"${nconf.get('EMAILS_TECH_ASSISTANCE_EMAIL')}"`,
|
||||
PRESS_ENQUIRY_EMAIL: `"${nconf.get('EMAILS_PRESS_ENQUIRY_EMAIL')}"`,
|
||||
},
|
||||
};
|
||||
|
||||
'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY GOOGLE_CLIENT_ID AMPLITUDE_KEY PUSHER:KEY PUSHER:ENABLED LOGGLY_CLIENT_TOKEN'
|
||||
'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY GOOGLE_CLIENT_ID AMPLITUDE_KEY LOGGLY_CLIENT_TOKEN'
|
||||
.split(' ')
|
||||
.forEach(key => {
|
||||
env[key] = `"${nconf.get(key)}"`;
|
||||
|
||||
@@ -11,22 +11,23 @@ div
|
||||
#app(:class='{"casting-spell": castingSpell}')
|
||||
banned-account-modal
|
||||
amazon-payments-modal(v-if='!isStaticPage')
|
||||
payments-success-modal
|
||||
snackbars
|
||||
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
||||
template(v-else)
|
||||
template(v-if="isUserLoaded")
|
||||
div.resting-banner(v-show="showRestingBanner", ref="restingBanner")
|
||||
.resting-banner(v-show="showRestingBanner", ref="restingBanner")
|
||||
span.content
|
||||
span.label.d-inline.d-sm-none {{ $t('innCheckOutBannerShort') }}
|
||||
span.label.d-none.d-sm-inline {{ $t('innCheckOutBanner') }}
|
||||
span.separator |
|
||||
span.resume(@click="resumeDamage()") {{ $t('resumeDamage') }}
|
||||
div.closepadding(@click="hideBanner()")
|
||||
.closepadding(@click="hideBanner()")
|
||||
span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close")
|
||||
notifications-display
|
||||
app-menu(:class='{"restingInn": showRestingBanner}' :style="{ marginTop: bannerHeight + 'px' }")
|
||||
app-menu
|
||||
.container-fluid
|
||||
app-header(:class='{"restingInn": showRestingBanner}')
|
||||
app-header
|
||||
buyModal(
|
||||
:item="selectedItemToBuy || {}",
|
||||
:withPin="true",
|
||||
@@ -49,6 +50,13 @@ div
|
||||
<style lang='scss' scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
#app {
|
||||
height: calc(100% - 56px); /* 56px is the menu */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#loading-screen-inapp {
|
||||
#melior {
|
||||
margin: 0 auto;
|
||||
@@ -78,6 +86,11 @@ div
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
overflow-x: hidden;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.notification {
|
||||
border-radius: 1000px;
|
||||
background-color: $green-10;
|
||||
@@ -88,42 +101,10 @@ div
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
overflow-x: hidden;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: calc(100% - 56px); /* 56px is the menu */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang='scss'>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
/* @TODO: The modal-open class is not being removed. Let's try this for now */
|
||||
.modal {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
.modal-backdrop.show {
|
||||
opacity: .9 !important;
|
||||
background-color: $purple-100 !important;
|
||||
}
|
||||
|
||||
/* Push progress bar above modals */
|
||||
#nprogress .bar {
|
||||
z-index: 1600 !important; /* Must stay above nav bar */
|
||||
}
|
||||
|
||||
.resting-banner {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
background-color: $blue-10;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1300;
|
||||
display: flex;
|
||||
@@ -139,14 +120,10 @@ div
|
||||
.closepadding {
|
||||
margin: 11px 24px;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
position: relative;
|
||||
right: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
|
||||
span svg path {
|
||||
stroke: $blue-500;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
@@ -169,6 +146,30 @@ div
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang='scss'>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.closepadding span svg path {
|
||||
stroke: #FFF;
|
||||
opacity: 0.48;
|
||||
}
|
||||
|
||||
/* @TODO: The modal-open class is not being removed. Let's try this for now */
|
||||
.modal {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
.modal-backdrop.show {
|
||||
opacity: .9 !important;
|
||||
background-color: $purple-100 !important;
|
||||
}
|
||||
|
||||
/* Push progress bar above modals */
|
||||
#nprogress .bar {
|
||||
z-index: 1600 !important; /* Must stay above nav bar */
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { loadProgressBar } from 'axios-progress-bar';
|
||||
@@ -185,7 +186,10 @@ import SelectMembersModal from 'client/components/selectMembersModal.vue';
|
||||
import notifications from 'client/mixins/notifications';
|
||||
import { setup as setupPayments } from 'client/libs/payments';
|
||||
import amazonPaymentsModal from 'client/components/payments/amazonModal';
|
||||
import paymentsSuccessModal from 'client/components/payments/successModal';
|
||||
|
||||
import spellsMixin from 'client/mixins/spells';
|
||||
import { CONSTANTS, getLocalSetting, removeLocalSetting } from 'client/libs/userlocalManager';
|
||||
|
||||
import svgClose from 'assets/svg/close.svg';
|
||||
import bannedAccountModal from 'client/components/bannedAccountModal';
|
||||
@@ -205,6 +209,7 @@ export default {
|
||||
SelectMembersModal,
|
||||
amazonPaymentsModal,
|
||||
bannedAccountModal,
|
||||
paymentsSuccessModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -220,7 +225,6 @@ export default {
|
||||
loading: true,
|
||||
currentTipNumber: 0,
|
||||
bannerHidden: false,
|
||||
bannerHeight: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -313,6 +317,7 @@ export default {
|
||||
const errorMessage = errorData.message || errorData;
|
||||
|
||||
// Check for conditions to reset the user auth
|
||||
// TODO use a specific error like NotificationNotFound instead of checking for the string
|
||||
const invalidUserMessage = [this.$t('invalidCredentials'), 'Missing authentication headers.'];
|
||||
if (invalidUserMessage.indexOf(errorMessage) !== -1) {
|
||||
this.$store.dispatch('auth:logout');
|
||||
@@ -322,12 +327,6 @@ export default {
|
||||
let snackbarTimeout = false;
|
||||
if (error.response.status === 502) snackbarTimeout = true;
|
||||
|
||||
const notificationNotFoundMessage = [
|
||||
this.$t('messageNotificationNotFound'),
|
||||
this.$t('messageNotificationNotFound', 'en'),
|
||||
];
|
||||
if (notificationNotFoundMessage.indexOf(errorMessage) !== -1) snackbarTimeout = true;
|
||||
|
||||
let errorsToShow = [];
|
||||
// show only the first error for each param
|
||||
let paramErrorsFound = {};
|
||||
@@ -341,13 +340,17 @@ export default {
|
||||
} else {
|
||||
errorsToShow.push(errorMessage);
|
||||
}
|
||||
// dispatch as one snackbar notification
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: errorsToShow.join(' '),
|
||||
type: 'error',
|
||||
timeout: snackbarTimeout,
|
||||
});
|
||||
|
||||
// Ignore NotificationNotFound errors, see https://github.com/HabitRPG/habitica/issues/10391
|
||||
if (errorData.error !== 'NotificationNotFound') {
|
||||
// dispatch as one snackbar notification
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: errorsToShow.join(' '),
|
||||
type: 'error',
|
||||
timeout: snackbarTimeout,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
@@ -418,14 +421,6 @@ export default {
|
||||
|
||||
this.hideLoadingScreen();
|
||||
|
||||
window.addEventListener('resize', this.setBannerOffset);
|
||||
// Adjust the positioning of the header banners
|
||||
this.$watch('showRestingBanner', () => {
|
||||
this.$nextTick(() => {
|
||||
this.setBannerOffset();
|
||||
});
|
||||
}, {immediate: true});
|
||||
|
||||
// Adjust the timezone offset
|
||||
if (this.user.preferences.timezoneOffset !== this.browserTimezoneOffset) {
|
||||
this.$store.dispatch('user:set', {
|
||||
@@ -433,6 +428,14 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
let appState = getLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
|
||||
if (appState) {
|
||||
appState = JSON.parse(appState);
|
||||
if (appState.paymentCompleted) {
|
||||
removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
|
||||
this.$root.$emit('habitica:payment-success', appState);
|
||||
}
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
// Load external scripts after the app has been rendered
|
||||
setupPayments();
|
||||
@@ -452,7 +455,6 @@ export default {
|
||||
this.$root.$off('bv::show::modal');
|
||||
this.$root.$off('buyModal::showItem');
|
||||
this.$root.$off('selectMembersModal::showItem');
|
||||
window.removeEventListener('resize', this.setBannerOffset);
|
||||
},
|
||||
mounted () {
|
||||
// Remove the index.html loading screen and now show the inapp loading
|
||||
@@ -611,22 +613,10 @@ export default {
|
||||
},
|
||||
hideBanner () {
|
||||
this.bannerHidden = true;
|
||||
this.setBannerOffset();
|
||||
},
|
||||
resumeDamage () {
|
||||
this.$store.dispatch('user:sleep');
|
||||
},
|
||||
setBannerOffset () {
|
||||
let contentPlacement = 0;
|
||||
if (this.showRestingBanner && this.$refs.restingBanner !== undefined) {
|
||||
contentPlacement = this.$refs.restingBanner.clientHeight;
|
||||
}
|
||||
this.bannerHeight = contentPlacement;
|
||||
let smartBanner = document.getElementsByClassName('smartbanner')[0];
|
||||
if (smartBanner !== undefined) {
|
||||
smartBanner.style.top = `${contentPlacement}px`;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,84 +1,72 @@
|
||||
.achievement-costumeContest6x {
|
||||
.promo_armoire_backgrounds_201901 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1136px -169px;
|
||||
width: 144px;
|
||||
height: 156px;
|
||||
}
|
||||
.promo_alligator {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 480px;
|
||||
height: 360px;
|
||||
}
|
||||
.promo_animal_tails {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -994px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_armoire_backgrounds_201811 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -481px -568px;
|
||||
background-position: -334px 0px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_frost_potions {
|
||||
.promo_bird_buddies_bundle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -421px -723px;
|
||||
width: 417px;
|
||||
background-position: 0px -401px;
|
||||
width: 420px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_ios {
|
||||
.promo_mystery_201901 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -361px;
|
||||
width: 375px;
|
||||
height: 361px;
|
||||
background-position: -758px -148px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201810 {
|
||||
.promo_npc_alex {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1136px 0px;
|
||||
width: 294px;
|
||||
height: 168px;
|
||||
background-position: -758px -444px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
}
|
||||
.promo_oddballs_bundle {
|
||||
.promo_seasonal_shop {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -481px -420px;
|
||||
background-position: -921px -444px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
}
|
||||
.promo_snow_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -253px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1281px -169px;
|
||||
background-position: -1041px -148px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_turkey_day_2018 {
|
||||
.promo_winter_wonderland_2019 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -723px;
|
||||
background-position: -758px 0px;
|
||||
width: 402px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_wintery_skins {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -549px;
|
||||
width: 420px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_veteran_pets {
|
||||
.customize-option.promo_wintery_skins {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -871px;
|
||||
width: 363px;
|
||||
height: 141px;
|
||||
background-position: -25px -564px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.scene_nametag {
|
||||
.scene_apollo {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -481px 0px;
|
||||
width: 512px;
|
||||
height: 208px;
|
||||
background-position: -758px -296px;
|
||||
width: 279px;
|
||||
height: 147px;
|
||||
}
|
||||
.scene_sleep {
|
||||
.scene_eating_healthy {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -481px -209px;
|
||||
width: 390px;
|
||||
height: 210px;
|
||||
}
|
||||
.scene_veteran_pets {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1136px -326px;
|
||||
width: 242px;
|
||||
height: 62px;
|
||||
background-position: 0px 0px;
|
||||
width: 333px;
|
||||
height: 252px;
|
||||
}
|
||||
|
||||
@@ -1,402 +1,402 @@
|
||||
.quest_TEMPLATE_FOR_MISSING_IMAGE {
|
||||
.npc_justin {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -502px -1546px;
|
||||
width: 221px;
|
||||
height: 39px;
|
||||
background-position: -1844px -856px;
|
||||
width: 84px;
|
||||
height: 120px;
|
||||
}
|
||||
.npc_matt {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -214px -1535px;
|
||||
width: 195px;
|
||||
height: 138px;
|
||||
}
|
||||
.background_dysheartener {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px 0px;
|
||||
width: 306px;
|
||||
height: 202px;
|
||||
}
|
||||
.banner_flair_dysheartener {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1929px -856px;
|
||||
width: 69px;
|
||||
height: 18px;
|
||||
}
|
||||
.phobia_dysheartener {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1187px -880px;
|
||||
width: 201px;
|
||||
height: 195px;
|
||||
}
|
||||
.quest_alligator {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1627px -1079px;
|
||||
width: 201px;
|
||||
height: 213px;
|
||||
}
|
||||
.quest_armadillo {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -435px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_atom1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -885px -1315px;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_atom2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1387px -1315px;
|
||||
width: 207px;
|
||||
height: 138px;
|
||||
}
|
||||
.quest_atom3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -747px -440px;
|
||||
width: 216px;
|
||||
height: 180px;
|
||||
}
|
||||
.quest_axolotl {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px -655px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_badger {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -655px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_basilist {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -191px -1710px;
|
||||
width: 189px;
|
||||
height: 141px;
|
||||
}
|
||||
.quest_beetle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1627px -1293px;
|
||||
width: 204px;
|
||||
height: 201px;
|
||||
}
|
||||
.quest_bunny {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -967px -660px;
|
||||
width: 210px;
|
||||
height: 186px;
|
||||
}
|
||||
.quest_butterfly {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -967px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_cheetah {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -875px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_cow {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1844px 0px;
|
||||
width: 174px;
|
||||
height: 213px;
|
||||
}
|
||||
.quest_dilatory {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1320px -660px;
|
||||
background-position: -660px -875px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_dilatoryDistress1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1540px -1085px;
|
||||
background-position: -1627px -868px;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
}
|
||||
.quest_dilatoryDistress2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -570px;
|
||||
background-position: -1844px -422px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_dilatoryDistress3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px -232px;
|
||||
background-position: -1187px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_dilatory_derby {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -672px;
|
||||
background-position: -440px -875px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_dustbunnies {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -232px;
|
||||
background-position: -1187px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_egg {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -362px;
|
||||
background-position: -1844px -214px;
|
||||
width: 165px;
|
||||
height: 207px;
|
||||
}
|
||||
.quest_evilsanta {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -1174px;
|
||||
background-position: -1844px -724px;
|
||||
width: 118px;
|
||||
height: 131px;
|
||||
}
|
||||
.quest_evilsanta2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -452px;
|
||||
background-position: -220px -1095px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_falcon {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px -452px;
|
||||
background-position: -440px -1095px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_ferret {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -452px;
|
||||
background-position: -660px -1095px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_frog {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1100px -1112px;
|
||||
background-position: -440px -1315px;
|
||||
width: 221px;
|
||||
height: 213px;
|
||||
}
|
||||
.quest_ghost_stag {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -880px 0px;
|
||||
background-position: -1100px -1095px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_goldenknight1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -880px -220px;
|
||||
background-position: -1407px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_goldenknight2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -1546px;
|
||||
background-position: -1136px -1315px;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_goldenknight3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px 0px;
|
||||
background-position: 0px -203px;
|
||||
width: 219px;
|
||||
height: 231px;
|
||||
}
|
||||
.quest_gryphon {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1314px -1332px;
|
||||
background-position: -527px -220px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_guineapig {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -672px;
|
||||
background-position: -1407px -880px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_harpy {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -660px -672px;
|
||||
background-position: 0px -1315px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_hedgehog {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px -1332px;
|
||||
background-position: -1407px -1100px;
|
||||
width: 219px;
|
||||
height: 186px;
|
||||
}
|
||||
.quest_hippo {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1100px 0px;
|
||||
background-position: -220px -1315px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_horse {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1100px -220px;
|
||||
background-position: -1407px -660px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_kangaroo {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1100px -440px;
|
||||
background-position: -1407px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_kraken {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -663px -1332px;
|
||||
background-position: -307px -220px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_lostMasterclasser1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -892px;
|
||||
background-position: -1407px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_lostMasterclasser2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px -892px;
|
||||
background-position: -880px -1095px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_lostMasterclasser3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -892px;
|
||||
background-position: 0px -1095px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_mayhemMistiflying1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -872px;
|
||||
background-position: -1844px -573px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_mayhemMistiflying2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -880px -892px;
|
||||
background-position: -1187px -660px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_mayhemMistiflying3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1100px -892px;
|
||||
background-position: -1187px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_monkey {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1320px 0px;
|
||||
background-position: -880px -875px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moon1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1540px -217px;
|
||||
background-position: -1627px -651px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_moon2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1320px -440px;
|
||||
background-position: -220px -875px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moon3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px 0px;
|
||||
background-position: -967px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1320px -880px;
|
||||
background-position: -967px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -1112px;
|
||||
background-position: -660px -655px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px -1112px;
|
||||
background-position: 0px -655px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_nudibranch {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1540px 0px;
|
||||
background-position: -1627px -434px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_octopus {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -1332px;
|
||||
background-position: -662px -1315px;
|
||||
width: 222px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_owl {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -660px -1112px;
|
||||
background-position: -747px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_peacock {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1540px -868px;
|
||||
background-position: -1627px -217px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_penguin {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -178px;
|
||||
background-position: 0px -1710px;
|
||||
width: 190px;
|
||||
height: 183px;
|
||||
}
|
||||
.quest_pterodactyl {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px -1112px;
|
||||
background-position: -747px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_rat {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1320px -220px;
|
||||
background-position: -220px -435px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_rock {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1540px -434px;
|
||||
background-position: -1627px 0px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_rooster {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1531px -1332px;
|
||||
background-position: 0px -1535px;
|
||||
width: 213px;
|
||||
height: 174px;
|
||||
}
|
||||
.quest_sabretooth {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -660px -892px;
|
||||
background-position: 0px -435px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_seaserpent {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1100px -660px;
|
||||
background-position: -527px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_sheep {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -880px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_slime {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -220px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_sloth {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -880px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_snail {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -1332px;
|
||||
width: 219px;
|
||||
height: 213px;
|
||||
}
|
||||
.quest_snake {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1322px -1112px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_spider {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -251px -1546px;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_squirrel {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -660px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_stoikalmCalamity1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -721px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_stoikalmCalamity2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -660px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_stoikalmCalamity3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -660px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_taskwoodsTerror1 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px -1023px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_taskwoodsTerror2 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1540px -651px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_taskwoodsTerror3 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: 0px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_treeling {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1097px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_trex {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -1757px 0px;
|
||||
width: 204px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_trex_undead {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -880px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_triceratops {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -440px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_turtle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-11.png');
|
||||
background-position: -880px -1112px;
|
||||
background-position: -307px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 447 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 546 KiB After Width: | Height: | Size: 545 KiB |
|
Before Width: | Height: | Size: 500 KiB After Width: | Height: | Size: 532 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 418 KiB After Width: | Height: | Size: 384 KiB |
|
Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 287 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 148 KiB |