Compare commits
164 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d6e8c8729 | |||
| 4659b1cc5c | |||
| 06c58bfae2 | |||
| 568e8840ed | |||
| aad6130b21 | |||
| 88d48f1e5d | |||
| 413626a971 | |||
| 26f39c9db6 | |||
| c5e0bcfb0e | |||
| c6c0e3660b | |||
| d00131fc4a | |||
| f7220e7e8c | |||
| bf6dde6e63 | |||
| c3024e5e58 | |||
| ca8b7f6b67 | |||
| 521077ed4f | |||
| f72f71fd32 | |||
| 18b04e713e | |||
| 6754c43317 | |||
| 0b13ba822e | |||
| 9071fa0073 | |||
| 7a5f01d516 | |||
| 0f3f54548d | |||
| c84dc40c7d | |||
| 16b244d5c6 | |||
| 86a07a4949 | |||
| 31bbac1751 | |||
| e6dd0d5e82 | |||
| 88fece1422 | |||
| ecc18fc093 | |||
| 0a59b8e85b | |||
| d677f5cfc7 | |||
| 88f872ed50 | |||
| 605391e4e7 | |||
| ca90d88289 | |||
| a4951c6478 | |||
| 95285cd85a | |||
| 1a03f8d7ae | |||
| 5d0cbd2456 | |||
| cdc8473f60 | |||
| 11a4c1c95d | |||
| 625b159880 | |||
| bf8b2db6b3 | |||
| 83a1b9c34e | |||
| c350665076 | |||
| 89ee8b1648 | |||
| 75680ab6aa | |||
| 116ec0f9d9 | |||
| a89633d17b | |||
| 2e3dd27414 | |||
| 3af756a90d | |||
| a9195f0d96 | |||
| ab777f7006 | |||
| d918bc9f56 | |||
| 4a89ca3e11 | |||
| cdbbf93b74 | |||
| d822843bbf | |||
| ea3ed26f42 | |||
| 0da1144635 | |||
| 5f3539da19 | |||
| e4d006e5cd | |||
| 801b53857f | |||
| f8571ec5d5 | |||
| 78ba596504 | |||
| fe9521a63f | |||
| c4348d8e47 | |||
| 9b0ee6a726 | |||
| c23fad3077 | |||
| 6850a1fcb4 | |||
| bc477455bb | |||
| 88c56c9877 | |||
| 4cd272f99c | |||
| 0292501387 | |||
| 28b56256d2 | |||
| a4ee3aaa7e | |||
| 7a2432a1a0 | |||
| 0bf515d0f9 | |||
| cbed198f02 | |||
| 90d77cee81 | |||
| 3668a9d3ac | |||
| 052c653cd3 | |||
| 3aa7b4b631 | |||
| 045378b820 | |||
| a256b6df03 | |||
| 92fdc13adf | |||
| dd29c60d87 | |||
| 78fd79931e | |||
| 5055a57ae9 | |||
| 3fa0b72ffe | |||
| b3f9fd09c6 | |||
| 30437e158e | |||
| 19ba1290f6 | |||
| c509c8e04f | |||
| 00d4393024 | |||
| c502b1997b | |||
| 4435862ff2 | |||
| e901850a6f | |||
| 48bbc22fb4 | |||
| c1e5d8b573 | |||
| 6951b79b95 | |||
| 1c3a12f37d | |||
| 3c71748a1b | |||
| 8e56222fc7 | |||
| ac3ac73737 | |||
| 8004c1e759 | |||
| d9e50e7632 | |||
| 4cd742c19a | |||
| 6bc6c09c75 | |||
| 0cbf9354cc | |||
| 03ca65f2b8 | |||
| ea74271484 | |||
| 0897367538 | |||
| 2566fcecb7 | |||
| c99863f339 | |||
| 71d632f6f0 | |||
| 06c53677f6 | |||
| aa91c5dbae | |||
| 5b7c7b77c8 | |||
| d3d221dccb | |||
| 53c610236b | |||
| 548cc2ceb0 | |||
| bf03ac8e44 | |||
| 1853113aed | |||
| 1ce1ed7c6b | |||
| 33a39d3683 | |||
| e4f5950ffc | |||
| f2d81a8d9c | |||
| 43b6f71044 | |||
| 06de1670b4 | |||
| 7fd2522e93 | |||
| acb4b79078 | |||
| 8299982484 | |||
| 207b1e91ca | |||
| aee21edd5f | |||
| 6e1bbd05bc | |||
| 6fb4c1b576 | |||
| 7c5bd526b1 | |||
| 187a9f5f19 | |||
| 7954763738 | |||
| a642d94443 | |||
| 39a112b605 | |||
| 8c8f0ea201 | |||
| ca8541e8c4 | |||
| 773d546e4f | |||
| 9ada0c9b02 | |||
| bd9bebe591 | |||
| fe62713809 | |||
| ca7272a987 | |||
| 2c60fade01 | |||
| ea2788ab2f | |||
| 5e111f6f6c | |||
| 136dcd27a9 | |||
| bf76f823d5 | |||
| 7a8857010e | |||
| a52bd66871 | |||
| f738f550e7 | |||
| 495bc9aa50 | |||
| 36fa3ab06f | |||
| 7422d020b1 | |||
| 5d0fe0aac3 | |||
| 60de7c8f21 | |||
| 137636cb40 | |||
| b1a4c1b4ff | |||
| fb80dd7c57 |
@@ -2,6 +2,9 @@ language: node_js
|
||||
node_js:
|
||||
- '6'
|
||||
sudo: required
|
||||
dist: precise
|
||||
services:
|
||||
- mongodb
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
@@ -17,7 +20,7 @@ install:
|
||||
before_script:
|
||||
- npm run test:build
|
||||
- cp config.json.example config.json
|
||||
- if [ $REQUIRES_SERVER ]; then until nc -z localhost 27017; do echo Waiting for MongoDB; sleep 1; done; export DISPLAY=:99; fi
|
||||
- sleep 15
|
||||
script:
|
||||
- npm run $TEST
|
||||
- if [ $COVERAGE ]; then ./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js; fi
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
FROM node:boron
|
||||
|
||||
# Install global packages
|
||||
RUN npm install -g gulp grunt-cli bower mocha
|
||||
|
||||
# Clone Habitica repo and install dependencies
|
||||
RUN mkdir -p /usr/src/habitrpg
|
||||
WORKDIR /usr/src/habitrpg
|
||||
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN npm install
|
||||
RUN bower install --allow-root
|
||||
|
||||
# Create Build dir
|
||||
RUN mkdir -p ./website/build
|
||||
|
||||
# Start Habitica
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
FROM node:boron
|
||||
|
||||
# Install global packages
|
||||
RUN npm install -g gulp grunt-cli bower mocha
|
||||
|
||||
# Clone Habitica repo and install dependencies
|
||||
RUN mkdir -p /usr/src/habitrpg
|
||||
WORKDIR /usr/src/habitrpg
|
||||
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN npm install
|
||||
RUN bower install --allow-root
|
||||
|
||||
# Create Build dir
|
||||
RUN mkdir -p ./website/build
|
||||
|
||||
# Start Habitica
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
|
||||
@@ -6,13 +6,14 @@ RUN npm install -g gulp grunt-cli bower mocha
|
||||
# Clone Habitica repo and install dependencies
|
||||
RUN mkdir -p /usr/src/habitrpg
|
||||
WORKDIR /usr/src/habitrpg
|
||||
RUN git clone --branch v3.99.0 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN git clone --branch v3.106.1 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN npm install
|
||||
RUN bower install --allow-root
|
||||
RUN gulp build:prod --force
|
||||
|
||||
# Create Build dir
|
||||
RUN mkdir -p ./website/build
|
||||
|
||||
# Start Habitica
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
CMD ["node", "./website/transpiled-babel/index.js"]
|
||||
@@ -66,7 +66,8 @@
|
||||
},
|
||||
"mode":"sandbox",
|
||||
"client_id":"client_id",
|
||||
"client_secret":"client_secret"
|
||||
"client_secret":"client_secret",
|
||||
"experience_profile_id": ""
|
||||
},
|
||||
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
|
||||
"LOGGLY_TOKEN": "token",
|
||||
|
||||
@@ -13,6 +13,7 @@ if (process.env.NODE_ENV === 'production') {
|
||||
require('./gulp/gulp-newstuff');
|
||||
require('./gulp/gulp-build');
|
||||
require('./gulp/gulp-babelify');
|
||||
require('./gulp/gulp-bootstrap');
|
||||
} else {
|
||||
require('glob').sync('./gulp/gulp-*').forEach(require);
|
||||
require('gulp').task('default', ['test']);
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Habitica in Kubernetes
|
||||
This is a set of sample Kubernetes configuration files to launch Habitica under AWS, both as a single-node web frontend as well as a multi-node web frontend.
|
||||
|
||||
## Prerequisites
|
||||
* An AWS account.
|
||||
* A working Kubernetes installation.
|
||||
* A basic understanding of how to use Kubernetes. https://kubernetes.io/
|
||||
* A persistent volume for MongoDB data.
|
||||
* Docker images of Habitica.
|
||||
+ You can use your own, or use the one included in the YAML files.
|
||||
+ If you use your own, you'll need a fork of the Habitica GitHub repo and your own Docker Hub repo, both of which are free.
|
||||
|
||||
## Before you begin
|
||||
1. Set up Kubernetes.
|
||||
2. Create an EBS volume for MongoDB data.
|
||||
+ Make a note of the name, you'll need it later.
|
||||
|
||||
## Starting MongoDB
|
||||
1. Edit mongo.yaml
|
||||
+ Find the volumeID line.
|
||||
+ Change the volume to the one created in the section above.
|
||||
2. Run the following commands:
|
||||
+ `kubectl.sh create -f mongo.yaml`
|
||||
+ `kubectl.sh create -f mongo-service.yaml`
|
||||
3. Wait for the MongoDB pod to start up.
|
||||
|
||||
## Starting a Single Web Frontend
|
||||
|
||||
1. Run the following commands:
|
||||
+ `kubectl.sh create -f habitica.yaml`
|
||||
+ `kubectl.sh create -f habitica-service.yaml`
|
||||
2. Wait for the frontend to start up.
|
||||
|
||||
## Starting Multi-node Web Frontend
|
||||
1. Run the following commands :
|
||||
+ `kubectl.sh create -f habitica-rc.yaml`
|
||||
+ `kubectl.sh create -f habitica-service.yaml`
|
||||
2. Wait for the frontend to start up.
|
||||
|
||||
## Accessing Your Habitica web interface
|
||||
Using `kubectl describe svc habiticaweb` get the hostname generated for the Habitica service. Open a browser and go to http://hostname:3000 to access the web front-end for the installations above.
|
||||
|
||||
## Shutting down
|
||||
Shutting down is basically done by reversing the steps above:
|
||||
+ `kubectl.sh delete -f habitica-service.yaml`
|
||||
+ `kubectl.sh delete -f habitica.yaml (or habitica-rc.yaml)`
|
||||
+ `kubectl.sh delete -f mongo-service.yaml`
|
||||
+ `kubectl.sh delete -f mongo.yaml`
|
||||
|
||||
You can also just shut down all of Kubernetes as well.
|
||||
|
||||
## Notes
|
||||
+ MongoDB data will be persistent! If you need to start with a fresh database, you'll need to remove the volume and re-create it.
|
||||
+ On AWS, you probably want to use at least t2.medium minion nodes for Kubernetes. The default t2.small is too small for more than two Habitica nodes.
|
||||
|
||||
## Future Plans
|
||||
+ Multi-node MongoDB.
|
||||
+ Monitoring
|
||||
+ Instructions for a better hostname. The default generated ones stink.
|
||||
+ More to come....
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: habitica
|
||||
labels:
|
||||
name: habitica
|
||||
spec:
|
||||
replicas: 4
|
||||
selector:
|
||||
name: habitica
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: habitica
|
||||
spec:
|
||||
containers:
|
||||
- name: habitica
|
||||
image: ksonney/habitrpg:latest
|
||||
env:
|
||||
- name: NODE_DB_URI
|
||||
value: mongodb://mongosvc/habitrpg
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
hostPort: 3000
|
||||
name: habitica
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
name: habiticaweb
|
||||
name: habiticaweb
|
||||
spec:
|
||||
ports:
|
||||
# the port that this service should serve on
|
||||
- port: 3000
|
||||
# label keys and values that must match in order to receive traffic for this service
|
||||
selector:
|
||||
name: habitica
|
||||
type: LoadBalancer
|
||||
@@ -0,0 +1,22 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: habitica
|
||||
labels:
|
||||
name: habitica
|
||||
spec:
|
||||
containers:
|
||||
# - image: mongo:latest
|
||||
# name: mongo
|
||||
# ports:
|
||||
# - containerPort: 27017
|
||||
# name: mongo
|
||||
- image: ksonney/habitrpg:latest
|
||||
name: habitica
|
||||
env:
|
||||
- name: NODE_DB_URI
|
||||
value: mongodb://mongosvc/habitrpg
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
hostPort: 3000
|
||||
name: habitica
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
name: mongosvc
|
||||
name: mongosvc
|
||||
spec:
|
||||
ports:
|
||||
# the port that this service should serve on
|
||||
- port: 27017
|
||||
# label keys and values that must match in order to receive traffic for this service
|
||||
selector:
|
||||
name: mongodb
|
||||
@@ -0,0 +1,28 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: mongodb
|
||||
labels:
|
||||
name: mongodb
|
||||
spec:
|
||||
containers:
|
||||
- resources:
|
||||
limits :
|
||||
cpu: 0.5
|
||||
image: mongo
|
||||
name: mongodb
|
||||
ports:
|
||||
- containerPort: 27017
|
||||
hostPort: 27017
|
||||
name: mongo
|
||||
volumeMounts:
|
||||
# # name must match the volume name below
|
||||
- name: mongo-persistent-storage
|
||||
# # mount path within the container
|
||||
mountPath: /data/db
|
||||
volumes:
|
||||
- name: mongo-persistent-storage
|
||||
awsElasticBlockStore:
|
||||
volumeID: aws://YOUR-REGION/YOUR-VOLNAME
|
||||
fsType: ext3
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
var migrationName = '20170711_orcas.js';
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Award Orca pets to owners of Orca mount, and Orca mount to everyone else
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers(lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'migration':{$ne:migrationName},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
'items.mounts',
|
||||
] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
var userPromises = users.map(updateUser);
|
||||
var lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(function () {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
var set = {};
|
||||
|
||||
if (user.items.mounts['Orca-Base']) {
|
||||
set = {'migration':migrationName, 'items.pets.Orca-Base': 5};
|
||||
} else {
|
||||
set = {'migration':migrationName, 'items.mounts.Orca-Base': true};
|
||||
}
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set:set});
|
||||
|
||||
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;
|
||||
@@ -0,0 +1,109 @@
|
||||
var migrationName = '20170731_naming_day.js';
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Award Royal Purple Gryphon Helm to Royal Purple Gryphon pet owners,
|
||||
* award Royal Purple Gryphon pet to Royal Purple Gryphon mount owners,
|
||||
* award Royal Purple Gryphon mount to everyone else
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers(lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'migration':{$ne:migrationName},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2017-01-01')},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
'items.mounts',
|
||||
'items.pets',
|
||||
] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
var userPromises = users.map(updateUser);
|
||||
var lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(function () {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
var set = {};
|
||||
var inc = {
|
||||
'achievements.habiticaDays': 1,
|
||||
'items.food.Cake_Skeleton': 1,
|
||||
'items.food.Cake_Base': 1,
|
||||
'items.food.Cake_CottonCandyBlue': 1,
|
||||
'items.food.Cake_CottonCandyPink': 1,
|
||||
'items.food.Cake_Shade': 1,
|
||||
'items.food.Cake_White': 1,
|
||||
'items.food.Cake_Golden': 1,
|
||||
'items.food.Cake_Zombie': 1,
|
||||
'items.food.Cake_Desert': 1,
|
||||
'items.food.Cake_Red': 1
|
||||
};
|
||||
|
||||
if (user.items.pets['Gryphon-RoyalPurple']) {
|
||||
set = {'migration':migrationName, 'items.gear.owned.head_special_namingDay2017': false};
|
||||
} else if (user.items.mounts['Gryphon-RoyalPurple']) {
|
||||
set = {'migration':migrationName, 'items.pets.Gryphon-RoyalPurple': 5};
|
||||
} else {
|
||||
set = {'migration':migrationName, 'items.mounts.Gryphon-RoyalPurple': true};
|
||||
}
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set: set, $inc: inc});
|
||||
|
||||
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;
|
||||
@@ -1,47 +1,47 @@
|
||||
import Bluebird from 'Bluebird';
|
||||
|
||||
import { model as Challenges } from '../../website/server/models/challenge';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
async function syncChallengeToMembers (challenges) {
|
||||
let challengSyncPromises = challenges.map(async function (challenge) {
|
||||
let users = await User.find({
|
||||
// _id: '',
|
||||
challenges: challenge._id,
|
||||
}).exec();
|
||||
|
||||
let promises = [];
|
||||
users.forEach(function (user) {
|
||||
promises.push(challenge.syncToUser(user));
|
||||
promises.push(challenge.save());
|
||||
promises.push(user.save());
|
||||
});
|
||||
|
||||
return Bluebird.all(promises);
|
||||
});
|
||||
|
||||
return await Bluebird.all(challengSyncPromises);
|
||||
}
|
||||
|
||||
async function syncChallenges (lastChallengeDate) {
|
||||
let query = {
|
||||
// _id: '',
|
||||
};
|
||||
|
||||
if (lastChallengeDate) {
|
||||
query.createdOn = { $lte: lastChallengeDate };
|
||||
}
|
||||
|
||||
let challengesFound = await Challenges.find(query)
|
||||
.limit(10)
|
||||
.sort('-createdAt')
|
||||
.exec();
|
||||
|
||||
let syncedChallenges = await syncChallengeToMembers(challengesFound)
|
||||
.catch(reason => console.error(reason));
|
||||
let lastChallenge = challengesFound[challengesFound.length - 1];
|
||||
if (lastChallenge) syncChallenges(lastChallenge.createdAt);
|
||||
return syncedChallenges;
|
||||
};
|
||||
|
||||
module.exports = syncChallenges;
|
||||
import Bluebird from 'Bluebird';
|
||||
|
||||
import { model as Challenges } from '../../website/server/models/challenge';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
async function syncChallengeToMembers (challenges) {
|
||||
let challengSyncPromises = challenges.map(async function (challenge) {
|
||||
let users = await User.find({
|
||||
// _id: '',
|
||||
challenges: challenge._id,
|
||||
}).exec();
|
||||
|
||||
let promises = [];
|
||||
users.forEach(function (user) {
|
||||
promises.push(challenge.syncToUser(user));
|
||||
promises.push(challenge.save());
|
||||
promises.push(user.save());
|
||||
});
|
||||
|
||||
return Bluebird.all(promises);
|
||||
});
|
||||
|
||||
return await Bluebird.all(challengSyncPromises);
|
||||
}
|
||||
|
||||
async function syncChallenges (lastChallengeDate) {
|
||||
let query = {
|
||||
// _id: '',
|
||||
};
|
||||
|
||||
if (lastChallengeDate) {
|
||||
query.createdOn = { $lte: lastChallengeDate };
|
||||
}
|
||||
|
||||
let challengesFound = await Challenges.find(query)
|
||||
.limit(10)
|
||||
.sort('-createdAt')
|
||||
.exec();
|
||||
|
||||
let syncedChallenges = await syncChallengeToMembers(challengesFound)
|
||||
.catch(reason => console.error(reason));
|
||||
let lastChallenge = challengesFound[challengesFound.length - 1];
|
||||
if (lastChallenge) syncChallenges(lastChallenge.createdAt);
|
||||
return syncedChallenges;
|
||||
};
|
||||
|
||||
module.exports = syncChallenges;
|
||||
|
||||
@@ -21,4 +21,4 @@ var processUsers = require('./groups/update-groups-with-group-plans');
|
||||
processUsers()
|
||||
.catch(function (err) {
|
||||
console.log(err)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@ var _id = '';
|
||||
var update = {
|
||||
$addToSet: {
|
||||
'purchased.plan.mysteryItems':{
|
||||
$each:['body_mystery_201706','back_mystery_201706']
|
||||
$each:['armor_mystery_201707','head_mystery_201707']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
var migrationName = 'tasks-set-yesterdaily';
|
||||
var authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
|
||||
var authorUuid = ''; //... own data is done
|
||||
|
||||
/*
|
||||
* Iterates over all tasks and sets the yseterDaily field to True
|
||||
*/
|
||||
|
||||
import monk from 'monk';
|
||||
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var dbTasks = monk(connectionString).get('tasks', { castIds: false });
|
||||
|
||||
function processTasks(lastId) {
|
||||
// specify a query to limit the affected tasks (empty for all tasks):
|
||||
var query = {
|
||||
yesterDaily: false,
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbTasks.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(updateTasks)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
|
||||
function updateTasks (tasks) {
|
||||
if (!tasks || tasks.length === 0) {
|
||||
console.warn('All appropriate tasks found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
var taskPromises = tasks.map(updatetask);
|
||||
var lasttask = tasks[tasks.length - 1];
|
||||
|
||||
return Promise.all(taskPromises)
|
||||
.then(function () {
|
||||
processtasks(lasttask._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updatetask (task) {
|
||||
count++;
|
||||
var set = {'yesterDaily': true};
|
||||
|
||||
dbTasks.update({_id: task._id}, {$set:set});
|
||||
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + task._id);
|
||||
if (task._id == authorUuid) console.warn(authorName + ' processed');
|
||||
}
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' tasks 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 = processtasks;
|
||||
@@ -0,0 +1,109 @@
|
||||
var migrationName = 'UserFromProdToTest';
|
||||
var authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
|
||||
var authorUuid = ''; //... own data is done
|
||||
|
||||
/*
|
||||
* This migraition will copy user data from prod to test
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var testConnectionSting = ''; // FOR TEST DATABASE
|
||||
var usersTest = monk(testConnectionSting).get('users', { castIds: false });
|
||||
var groupsTest = monk(testConnectionSting).get('groups', { castIds: false });
|
||||
var challengesTest = monk(testConnectionSting).get('challenges', { castIds: false });
|
||||
var tasksTest = monk(testConnectionSting).get('tasks', { castIds: false });
|
||||
|
||||
var monk2 = require('monk');
|
||||
var liveConnectString = ''; // FOR TEST DATABASE
|
||||
var userLive = monk2(liveConnectString).get('users', { castIds: false });
|
||||
var groupsLive = monk2(liveConnectString).get('groups', { castIds: false });
|
||||
var challengesLive = monk2(liveConnectString).get('challenges', { castIds: false });
|
||||
var tasksLive = monk2(liveConnectString).get('tasks', { castIds: false });
|
||||
|
||||
import uniq from 'lodash/uniq';
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
// Variabls for updating
|
||||
let userIds = [
|
||||
'206039c6-24e4-4b9f-8a31-61cbb9aa3f66',
|
||||
];
|
||||
|
||||
let groupIds = [];
|
||||
let challengeIds = [];
|
||||
let tasksIds = [];
|
||||
|
||||
async function processUsers () {
|
||||
let userPromises = [];
|
||||
//{_id: {$in: userIds}}
|
||||
|
||||
return userLive.find({guilds: 'b0764d64-8276-45a1-afa5-5ca9a5c64ca0'})
|
||||
.each((user, {close, pause, resume}) => {
|
||||
if (user.guilds.length > 0) groupIds = groupIds.concat(user.guilds);
|
||||
if (user.party._id) groupIds.push(user.party._id);
|
||||
if (user.challenges.length > 0) challengeIds = challengeIds.concat(user.challenges);
|
||||
if (user.tasksOrder.rewards.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.rewards);
|
||||
if (user.tasksOrder.todos.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.todos);
|
||||
if (user.tasksOrder.dailys.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.dailys);
|
||||
if (user.tasksOrder.habits.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.habits);
|
||||
|
||||
let userPromise = usersTest.update({'_id': user._id}, user, {upsert:true});
|
||||
userPromises.push(userPromise);
|
||||
}).then(() => {
|
||||
return Bluebird.all(userPromises);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Done User");
|
||||
});
|
||||
}
|
||||
|
||||
function processGroups () {
|
||||
let promises = [];
|
||||
let groupsToQuery = uniq(groupIds);
|
||||
return groupsLive.find({_id: {$in: groupsToQuery}})
|
||||
.each((group, {close, pause, resume}) => {
|
||||
let promise = groupsTest.update({_id: group._id}, group, {upsert:true});
|
||||
promises.push(promise);
|
||||
}).then(() => {
|
||||
return Bluebird.all(promises);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Done Group");
|
||||
});
|
||||
}
|
||||
|
||||
function processChallenges () {
|
||||
let promises = [];
|
||||
let challengesToQuery = uniq(challengeIds);
|
||||
return challengesLive.find({_id: {$in: challengesToQuery}})
|
||||
.each((challenge, {close, pause, resume}) => {
|
||||
let promise = challengesTest.update({_id: challenge._id}, challenge, {upsert:true});
|
||||
promises.push(promise);
|
||||
}).then(() => {
|
||||
return Bluebird.all(promises);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Done Challenge");
|
||||
});
|
||||
}
|
||||
|
||||
function processTasks () {
|
||||
let promises = [];
|
||||
let tasksToQuery = uniq(tasksIds);
|
||||
return tasksLive.find({_id: {$in: tasksToQuery}})
|
||||
.each((task, {close, pause, resume}) => {
|
||||
let promise = tasksTest.update({_id: task._id}, task, {upsert:true});
|
||||
promises.push(promise);
|
||||
}).then(() => {
|
||||
return Bluebird.all(promises);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Done Tasks");
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = async function prodToTest () {
|
||||
await processUsers();
|
||||
await processGroups();
|
||||
await processChallenges();
|
||||
await processTasks();
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"version": "3.99.0",
|
||||
"version": "3.106.1",
|
||||
"dependencies": {
|
||||
"@gulp-sourcemaps/map-sources": {
|
||||
"version": "1.0.0",
|
||||
@@ -4208,700 +4208,6 @@
|
||||
"from": "fs.realpath@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "1.1.1",
|
||||
"from": "fsevents@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.1.tgz",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"abbrev": {
|
||||
"version": "1.1.0",
|
||||
"from": "abbrev@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"from": "ansi-regex@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz"
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "2.2.1",
|
||||
"from": "ansi-styles@>=2.2.1 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.1.1",
|
||||
"from": "aproba@>=1.0.3 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"are-we-there-yet": {
|
||||
"version": "1.1.2",
|
||||
"from": "are-we-there-yet@>=1.1.2 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.2.3",
|
||||
"from": "asn1@>=0.2.3 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"assert-plus": {
|
||||
"version": "0.2.0",
|
||||
"from": "assert-plus@>=0.2.0 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"from": "asynckit@>=0.4.0 <0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"aws-sign2": {
|
||||
"version": "0.6.0",
|
||||
"from": "aws-sign2@>=0.6.0 <0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"aws4": {
|
||||
"version": "1.6.0",
|
||||
"from": "aws4@>=1.2.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "0.4.2",
|
||||
"from": "balanced-match@>=0.4.1 <0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz"
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.1",
|
||||
"from": "bcrypt-pbkdf@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"block-stream": {
|
||||
"version": "0.0.9",
|
||||
"from": "block-stream@*",
|
||||
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz"
|
||||
},
|
||||
"boom": {
|
||||
"version": "2.10.1",
|
||||
"from": "boom@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.6",
|
||||
"from": "brace-expansion@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz"
|
||||
},
|
||||
"buffer-shims": {
|
||||
"version": "1.0.0",
|
||||
"from": "buffer-shims@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz"
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.11.0",
|
||||
"from": "caseless@>=0.11.0 <0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"from": "chalk@>=1.1.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"from": "code-point-at@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz"
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.5",
|
||||
"from": "combined-stream@>=1.0.5 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz"
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.9.0",
|
||||
"from": "commander@>=2.9.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"from": "concat-map@0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"from": "console-control-strings@>=1.1.0 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz"
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"from": "core-util-is@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
|
||||
},
|
||||
"cryptiles": {
|
||||
"version": "2.0.5",
|
||||
"from": "cryptiles@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.1",
|
||||
"from": "dashdash@>=1.12.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"from": "assert-plus@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.2.0",
|
||||
"from": "debug@>=2.2.0 <2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.4.1",
|
||||
"from": "deep-extend@>=0.4.0 <0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"from": "delayed-stream@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
|
||||
},
|
||||
"delegates": {
|
||||
"version": "1.0.0",
|
||||
"from": "delegates@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"ecc-jsbn": {
|
||||
"version": "0.1.1",
|
||||
"from": "ecc-jsbn@>=0.1.1 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"from": "escape-string-regexp@>=1.0.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.0",
|
||||
"from": "extend@>=3.0.0 <3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"extsprintf": {
|
||||
"version": "1.0.2",
|
||||
"from": "extsprintf@1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz"
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"from": "forever-agent@>=0.6.1 <0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"form-data": {
|
||||
"version": "2.1.2",
|
||||
"from": "form-data@>=2.1.1 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"from": "fs.realpath@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
||||
},
|
||||
"fstream": {
|
||||
"version": "1.0.10",
|
||||
"from": "fstream@>=1.0.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.10.tgz"
|
||||
},
|
||||
"fstream-ignore": {
|
||||
"version": "1.0.5",
|
||||
"from": "fstream-ignore@>=1.0.5 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"gauge": {
|
||||
"version": "2.7.3",
|
||||
"from": "gauge@>=2.7.1 <2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"generate-function": {
|
||||
"version": "2.0.0",
|
||||
"from": "generate-function@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"generate-object-property": {
|
||||
"version": "1.2.0",
|
||||
"from": "generate-object-property@>=1.1.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"getpass": {
|
||||
"version": "0.1.6",
|
||||
"from": "getpass@>=0.1.1 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"from": "assert-plus@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.1",
|
||||
"from": "glob@>=7.0.5 <8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz"
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.1.11",
|
||||
"from": "graceful-fs@>=4.1.2 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz"
|
||||
},
|
||||
"graceful-readlink": {
|
||||
"version": "1.0.1",
|
||||
"from": "graceful-readlink@>=1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "2.0.6",
|
||||
"from": "har-validator@>=2.0.6 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"has-ansi": {
|
||||
"version": "2.0.0",
|
||||
"from": "has-ansi@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"from": "has-unicode@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"hawk": {
|
||||
"version": "3.1.3",
|
||||
"from": "hawk@>=3.1.3 <3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"hoek": {
|
||||
"version": "2.16.3",
|
||||
"from": "hoek@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "1.1.1",
|
||||
"from": "http-signature@>=1.1.0 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"from": "inflight@>=1.0.4 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"from": "inherits@>=2.0.1 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.4",
|
||||
"from": "ini@>=1.3.0 <1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"from": "is-fullwidth-code-point@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz"
|
||||
},
|
||||
"is-my-json-valid": {
|
||||
"version": "2.15.0",
|
||||
"from": "is-my-json-valid@>=2.12.4 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"is-property": {
|
||||
"version": "1.0.2",
|
||||
"from": "is-property@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"from": "is-typedarray@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"from": "isarray@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"from": "isstream@>=0.1.2 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"jodid25519": {
|
||||
"version": "1.0.2",
|
||||
"from": "jodid25519@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"jsbn": {
|
||||
"version": "0.1.1",
|
||||
"from": "jsbn@>=0.1.0 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"json-schema": {
|
||||
"version": "0.2.3",
|
||||
"from": "json-schema@0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"from": "json-stringify-safe@>=5.0.1 <5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"jsonpointer": {
|
||||
"version": "4.0.1",
|
||||
"from": "jsonpointer@>=4.0.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"jsprim": {
|
||||
"version": "1.3.1",
|
||||
"from": "jsprim@>=1.2.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.26.0",
|
||||
"from": "mime-db@>=1.26.0 <1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.26.0.tgz"
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.14",
|
||||
"from": "mime-types@>=2.1.7 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.14.tgz"
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.3",
|
||||
"from": "minimatch@>=3.0.2 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz"
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"from": "minimist@0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"from": "mkdirp@>=0.5.1 <0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
|
||||
},
|
||||
"ms": {
|
||||
"version": "0.7.1",
|
||||
"from": "ms@0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
"version": "0.6.33",
|
||||
"from": "node-pre-gyp@>=0.6.29 <0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.33.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"nopt": {
|
||||
"version": "3.0.6",
|
||||
"from": "nopt@>=3.0.6 <3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"npmlog": {
|
||||
"version": "4.0.2",
|
||||
"from": "npmlog@>=4.0.1 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.0.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"from": "number-is-nan@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz"
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.8.2",
|
||||
"from": "oauth-sign@>=0.8.1 <0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"from": "object-assign@>=4.1.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"from": "once@>=1.3.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"from": "path-is-absolute@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
|
||||
},
|
||||
"pinkie": {
|
||||
"version": "2.0.4",
|
||||
"from": "pinkie@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"pinkie-promise": {
|
||||
"version": "2.0.1",
|
||||
"from": "pinkie-promise@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"from": "process-nextick-args@>=1.0.6 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz"
|
||||
},
|
||||
"punycode": {
|
||||
"version": "1.4.1",
|
||||
"from": "punycode@>=1.4.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.3.1",
|
||||
"from": "qs@>=6.3.0 <6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.3.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.1.7",
|
||||
"from": "rc@>=1.1.6 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.1.7.tgz",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"from": "minimist@>=1.2.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.2.2",
|
||||
"from": "readable-stream@>=2.0.0 <3.0.0||>=1.1.13 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"request": {
|
||||
"version": "2.79.0",
|
||||
"from": "request@>=2.79.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.5.4",
|
||||
"from": "rimraf@>=2.5.4 <2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz"
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.3.0",
|
||||
"from": "semver@>=5.3.0 <5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"from": "set-blocking@>=2.0.0 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"from": "signal-exit@>=3.0.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"sntp": {
|
||||
"version": "1.0.9",
|
||||
"from": "sntp@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"sshpk": {
|
||||
"version": "1.10.2",
|
||||
"from": "sshpk@>=1.7.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.2.tgz",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"from": "assert-plus@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"from": "string_decoder@>=0.10.0 <0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"from": "string-width@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz"
|
||||
},
|
||||
"stringstream": {
|
||||
"version": "0.0.5",
|
||||
"from": "stringstream@>=0.0.4 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"from": "strip-ansi@>=3.0.1 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"from": "strip-json-comments@>=2.0.1 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "2.0.0",
|
||||
"from": "supports-color@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"tar": {
|
||||
"version": "2.2.1",
|
||||
"from": "tar@>=2.2.1 <2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz"
|
||||
},
|
||||
"tar-pack": {
|
||||
"version": "3.3.0",
|
||||
"from": "tar-pack@>=3.3.0 <3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.3.0.tgz",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"once": {
|
||||
"version": "1.3.3",
|
||||
"from": "once@>=1.3.3 <1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.1.5",
|
||||
"from": "readable-stream@>=2.1.4 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.3.2",
|
||||
"from": "tough-cookie@>=2.3.0 <2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.4.3",
|
||||
"from": "tunnel-agent@>=0.4.1 <0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"from": "tweetnacl@>=0.14.0 <0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"uid-number": {
|
||||
"version": "0.0.6",
|
||||
"from": "uid-number@>=0.0.6 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"from": "util-deprecate@>=1.0.1 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.0.1",
|
||||
"from": "uuid@>=3.0.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"verror": {
|
||||
"version": "1.3.6",
|
||||
"from": "verror@1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.0",
|
||||
"from": "wide-align@>=1.1.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"from": "wrappy@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.1",
|
||||
"from": "xtend@>=4.0.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"fstream": {
|
||||
"version": "1.0.11",
|
||||
"from": "fstream@>=1.0.2 <2.0.0",
|
||||
@@ -6128,6 +5434,11 @@
|
||||
"from": "he@>=1.1.0 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz"
|
||||
},
|
||||
"hellojs": {
|
||||
"version": "1.15.1",
|
||||
"from": "hellojs@>=1.15.1 <2.0.0",
|
||||
"resolved": "http://registry.npmjs.org/hellojs/-/hellojs-1.15.1.tgz"
|
||||
},
|
||||
"hmac-drbg": {
|
||||
"version": "1.0.1",
|
||||
"from": "hmac-drbg@>=1.0.0 <2.0.0",
|
||||
@@ -7717,7 +7028,7 @@
|
||||
"lazy-debug-legacy": {
|
||||
"version": "0.0.1",
|
||||
"from": "lazy-debug-legacy@>=0.0.0 <0.1.0",
|
||||
"resolved": "http://registry.npmjs.org/lazy-debug-legacy/-/lazy-debug-legacy-0.0.1.tgz"
|
||||
"resolved": "https://registry.npmjs.org/lazy-debug-legacy/-/lazy-debug-legacy-0.0.1.tgz"
|
||||
},
|
||||
"lazy-req": {
|
||||
"version": "1.1.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "3.99.0",
|
||||
"version": "3.107.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
@@ -31,7 +31,7 @@
|
||||
"bluebird": "^3.3.5",
|
||||
"body-parser": "^1.15.0",
|
||||
"bootstrap": "^4.0.0-alpha.6",
|
||||
"bootstrap-vue": "^0.15.8",
|
||||
"bootstrap-vue": "^0.16.1",
|
||||
"bower": "~1.3.12",
|
||||
"browserify": "~12.0.1",
|
||||
"compression": "^1.6.1",
|
||||
@@ -69,6 +69,7 @@
|
||||
"gulp-uglify": "^1.4.2",
|
||||
"gulp.spritesmith": "^4.1.0",
|
||||
"habitica-markdown": "^1.3.0",
|
||||
"hellojs": "^1.15.1",
|
||||
"html-webpack-plugin": "^2.8.1",
|
||||
"image-size": "~0.3.2",
|
||||
"in-app-purchase": "^1.1.6",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require("babel-register");
|
||||
require("babel-polyfill");
|
||||
// This file is used for creating paypal billing plans. PayPal doesn't have a web interface for setting up recurring
|
||||
// payment plan definitions, instead you have to create it via their REST SDK and keep it updated the same way. So this
|
||||
// file will be used once for initing your billing plan (then you get the resultant plan.id to store in config.json),
|
||||
@@ -7,10 +9,10 @@ var path = require('path');
|
||||
var nconf = require('nconf');
|
||||
var _ = require('lodash');
|
||||
var paypal = require('paypal-rest-sdk');
|
||||
var blocks = require('../../../../common').content.subscriptionBlocks;
|
||||
var blocks = require('../website/common').content.subscriptionBlocks;
|
||||
var live = nconf.get('PAYPAL:mode')=='live';
|
||||
|
||||
nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../../../config.json')));
|
||||
nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.json')));
|
||||
|
||||
var OP = 'create'; // list create update remove
|
||||
|
||||
@@ -49,6 +51,8 @@ _.each(blocks, function(block){
|
||||
});
|
||||
})
|
||||
|
||||
// @TODO: Add cli library for this
|
||||
|
||||
switch(OP) {
|
||||
case "list":
|
||||
paypal.billingPlan.list({status: 'ACTIVE'}, function(err, plans){
|
||||
@@ -91,4 +95,17 @@ switch(OP) {
|
||||
});
|
||||
break;
|
||||
case "remove": break;
|
||||
|
||||
case 'create-webprofile':
|
||||
let webexpinfo = {
|
||||
"name": "HabiticaProfile",
|
||||
"input_fields": {
|
||||
"no_shipping": 1,
|
||||
},
|
||||
};
|
||||
|
||||
paypal.webProfile.create(webexpinfo, (error, result) => {
|
||||
console.log(error, result)
|
||||
})
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -32,18 +32,20 @@ describe('POST /challenges/:challengeId/leave', () => {
|
||||
let group;
|
||||
let challenge;
|
||||
let notInChallengeUser;
|
||||
let notInGroupLeavingUser;
|
||||
let leavingUser;
|
||||
let taskText;
|
||||
|
||||
beforeEach(async () => {
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
members: 2,
|
||||
members: 3,
|
||||
});
|
||||
|
||||
groupLeader = populatedGroup.groupLeader;
|
||||
group = populatedGroup.group;
|
||||
leavingUser = populatedGroup.members[0];
|
||||
notInChallengeUser = populatedGroup.members[1];
|
||||
notInGroupLeavingUser = populatedGroup.members[2];
|
||||
|
||||
challenge = await generateChallenge(groupLeader, group);
|
||||
|
||||
@@ -55,17 +57,16 @@ describe('POST /challenges/:challengeId/leave', () => {
|
||||
|
||||
await leavingUser.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await notInGroupLeavingUser.post(`/challenges/${challenge._id}/join`);
|
||||
await notInGroupLeavingUser.post(`/groups/${group._id}/leave`, {
|
||||
keepChallenges: 'remain-in-challenges',
|
||||
});
|
||||
|
||||
await challenge.sync();
|
||||
});
|
||||
|
||||
it('returns an error when user doesn\'t have permissions to view the challenge', async () => {
|
||||
let unauthorizedUser = await generateUser();
|
||||
|
||||
await expect(unauthorizedUser.post(`/challenges/${challenge._id}/leave`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('challengeNotFound'),
|
||||
});
|
||||
it('lets user leave when not a member of the challenge group', async () => {
|
||||
await expect(notInGroupLeavingUser.post(`/challenges/${challenge._id}/leave`)).to.eventually.be.ok;
|
||||
});
|
||||
|
||||
it('returns an error when user isn\'t a member of the challenge', async () => {
|
||||
|
||||
@@ -114,6 +114,26 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
|
||||
await expect(winningUser.sync()).to.eventually.have.property('balance', oldBalance + challenge.prize / 4);
|
||||
});
|
||||
|
||||
it('doesn\'t gives winner gems if group policy prevents it', async () => {
|
||||
let oldBalance = winningUser.balance;
|
||||
let oldLeaderBalance = (await groupLeader.sync()).balance;
|
||||
|
||||
await winningUser.update({
|
||||
'purchased.plan.customerId': 'group-plan',
|
||||
});
|
||||
await group.update({
|
||||
'leaderOnly.getGems': true,
|
||||
'purchased.plan.customerId': 123,
|
||||
});
|
||||
|
||||
await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
|
||||
|
||||
await sleep(0.5);
|
||||
|
||||
await expect(winningUser.sync()).to.eventually.have.property('balance', oldBalance);
|
||||
await expect(groupLeader.sync()).to.eventually.have.property('balance', oldLeaderBalance + challenge.prize / 4);
|
||||
});
|
||||
|
||||
it('doesn\'t refund gems to group leader', async () => {
|
||||
let oldBalance = (await groupLeader.sync()).balance;
|
||||
|
||||
|
||||
@@ -10,11 +10,17 @@ import {
|
||||
TAVERN_ID,
|
||||
} from '../../../../../website/server/models/group';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
import { IncomingWebhook } from '@slack/client';
|
||||
import nconf from 'nconf';
|
||||
|
||||
const BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
describe('POST /chat', () => {
|
||||
let user, groupWithChat, member, additionalMember;
|
||||
let testMessage = 'Test Message';
|
||||
let testBannedWordMessage = 'TEST_PLACEHOLDER_SWEAR_WORD_HERE';
|
||||
let testSlurMessage = 'message with TEST_PLACEHOLDER_SLUR_WORD_HERE';
|
||||
|
||||
before(async () => {
|
||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
@@ -166,6 +172,114 @@ describe('POST /chat', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('banned slur', () => {
|
||||
beforeEach(() => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
sandbox.stub(IncomingWebhook.prototype, 'send');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('errors and revokes privileges when chat message contains a banned slur', async () => {
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: testSlurMessage})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedSlurUsed'),
|
||||
});
|
||||
|
||||
// Email sent to mods
|
||||
await sleep(0.5);
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
expect(email.sendTxn.args[0][1]).to.be.eql('slur-report-to-mods');
|
||||
|
||||
// Slack message to mods
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `${user.profile.name} (${user.id}) tried to post a slur`,
|
||||
attachments: [{
|
||||
fallback: 'Slur Message',
|
||||
color: 'danger',
|
||||
author_name: `${user.profile.name} - ${user.auth.local.email} - ${user._id}`,
|
||||
title: 'Slur in Test Guild',
|
||||
title_link: `${BASE_URL}/#/options/groups/guilds/${groupWithChat.id}`,
|
||||
text: testSlurMessage,
|
||||
// footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message>/),
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
// Chat privileges are revoked
|
||||
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
|
||||
// Restore chat privileges to continue testing
|
||||
user.flags.chatRevoked = false;
|
||||
await user.update({'flags.chatRevoked': false});
|
||||
});
|
||||
|
||||
it('does not allow slurs in private groups', async () => {
|
||||
let { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Party',
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
await expect(members[0].post(`/groups/${group._id}/chat`, { message: testSlurMessage})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('bannedSlurUsed'),
|
||||
});
|
||||
|
||||
// Email sent to mods
|
||||
await sleep(0.5);
|
||||
expect(email.sendTxn).to.be.calledThrice;
|
||||
expect(email.sendTxn.args[2][1]).to.be.eql('slur-report-to-mods');
|
||||
|
||||
// Slack message to mods
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||
/* eslint-disable camelcase */
|
||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||
text: `${members[0].profile.name} (${members[0].id}) tried to post a slur`,
|
||||
attachments: [{
|
||||
fallback: 'Slur Message',
|
||||
color: 'danger',
|
||||
author_name: `${members[0].profile.name} - ${members[0].auth.local.email} - ${members[0]._id}`,
|
||||
title: 'Slur in Party - (private party)',
|
||||
title_link: undefined,
|
||||
text: testSlurMessage,
|
||||
// footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message>/),
|
||||
mrkdwn_in: [
|
||||
'text',
|
||||
],
|
||||
}],
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
// Chat privileges are revoked
|
||||
await expect(members[0].post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('chatPrivilegesRevoked'),
|
||||
});
|
||||
|
||||
// Restore chat privileges to continue testing
|
||||
members[0].flags.chatRevoked = false;
|
||||
await members[0].update({'flags.chatRevoked': false});
|
||||
});
|
||||
});
|
||||
|
||||
it('does not error when sending a message to a private guild with a user with revoked chat', async () => {
|
||||
let { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
|
||||
@@ -16,6 +16,12 @@ describe('GET /groups', () => {
|
||||
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
|
||||
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5;
|
||||
const GUILD_PER_PAGE = 30;
|
||||
let categories = [{
|
||||
slug: 'newCat',
|
||||
name: 'New Category',
|
||||
}];
|
||||
let publicGuildNotMember;
|
||||
let privateGuildUserIsMemberOf;
|
||||
|
||||
before(async () => {
|
||||
await resetHabiticaDB();
|
||||
@@ -31,16 +37,18 @@ describe('GET /groups', () => {
|
||||
await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id]});
|
||||
await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`);
|
||||
|
||||
await generateGroup(leader, {
|
||||
publicGuildNotMember = await generateGroup(leader, {
|
||||
name: 'public guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
categories,
|
||||
});
|
||||
|
||||
let privateGuildUserIsMemberOf = await generateGroup(leader, {
|
||||
privateGuildUserIsMemberOf = await generateGroup(leader, {
|
||||
name: 'private guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
categories,
|
||||
});
|
||||
await leader.post(`/groups/${privateGuildUserIsMemberOf._id}/invite`, { uuids: [user._id]});
|
||||
await user.post(`/groups/${privateGuildUserIsMemberOf._id}/join`);
|
||||
@@ -100,6 +108,50 @@ describe('GET /groups', () => {
|
||||
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS);
|
||||
});
|
||||
|
||||
describe('filters', () => {
|
||||
it('returns public guilds filtered by category', async () => {
|
||||
let guilds = await user.get(`/groups?type=publicGuilds&categories=${categories[0].slug}`);
|
||||
|
||||
expect(guilds[0]._id).to.equal(publicGuildNotMember._id);
|
||||
});
|
||||
|
||||
it('returns private guilds filtered by category', async () => {
|
||||
let guilds = await user.get(`/groups?type=privateGuilds&categories=${categories[0].slug}`);
|
||||
|
||||
expect(guilds[0]._id).to.equal(privateGuildUserIsMemberOf._id);
|
||||
});
|
||||
|
||||
it('filters public guilds by size', async () => {
|
||||
await generateGroup(user, {
|
||||
name: 'guild1',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
// @TODO: anyway to set higher memberCount in tests right now?
|
||||
|
||||
let guilds = await user.get('/groups?type=publicGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('filters private guilds by size', async () => {
|
||||
await generateGroup(user, {
|
||||
name: 'guild1',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
memberCount: 1,
|
||||
});
|
||||
|
||||
// @TODO: anyway to set higher memberCount in tests right now?
|
||||
|
||||
let guilds = await user.get('/groups?type=privateGuilds&minMemberCount=3');
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('public guilds pagination', () => {
|
||||
it('req.query.paginate must be a boolean string', async () => {
|
||||
await expect(user.get('/groups?paginate=aString&type=publicGuilds'))
|
||||
@@ -149,8 +201,8 @@ describe('GET /groups', () => {
|
||||
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=1'))
|
||||
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
|
||||
let page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2'))
|
||||
.to.eventually.have.a.lengthOf(1 + 2); // 1 created now, 2 by other tests
|
||||
expect(page2[2].name).to.equal('guild with less members');
|
||||
.to.eventually.have.a.lengthOf(1 + 3); // 1 created now, 3 by other tests
|
||||
expect(page2[3].name).to.equal('guild with less members');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -66,11 +66,25 @@ describe('GET /groups/:groupId/members', () => {
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
});
|
||||
|
||||
it('req.query.includeAllPublicFields === true only works with parties', async () => {
|
||||
it('req.query.includeAllPublicFields === true works with guilds', async () => {
|
||||
let group = await generateGroup(user, {type: 'guild', name: generateUUID()});
|
||||
let res = await user.get(`/groups/${group._id}/members?includeAllPublicFields=true`);
|
||||
expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
|
||||
expect(res[0].profile).to.have.all.keys(['name']);
|
||||
let [memberRes] = await user.get(`/groups/${group._id}/members?includeAllPublicFields=true`);
|
||||
|
||||
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||
'backer', 'contributor', 'auth', 'items', 'inbox',
|
||||
]);
|
||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||
'size', 'hair', 'skin', 'shirt',
|
||||
'chair', 'costume', 'sleep', 'background', 'tasks',
|
||||
].sort());
|
||||
|
||||
expect(memberRes.stats.maxMP).to.exist;
|
||||
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
|
||||
expect(memberRes.stats.toNextLevel).to.equal(common.tnl(memberRes.stats.lvl));
|
||||
expect(memberRes.inbox.optOut).to.exist;
|
||||
expect(memberRes.inbox.messages).to.not.exist;
|
||||
});
|
||||
|
||||
it('populates all public fields if req.query.includeAllPublicFields === true and it is a party', async () => {
|
||||
|
||||
@@ -220,7 +220,7 @@ describe('POST /group/:groupId/join', () => {
|
||||
it('clears invitation from user when joining party', async () => {
|
||||
await invitedUser.post(`/groups/${party._id}/join`);
|
||||
|
||||
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id');
|
||||
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.parties[0].id');
|
||||
});
|
||||
|
||||
it('increments memberCount when joining party', async () => {
|
||||
|
||||
@@ -247,7 +247,7 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
|
||||
let userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
expect(userWithoutInvitation.invitations.party).to.be.empty;
|
||||
expect(userWithoutInvitation.invitations.parties[0]).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ describe('POST /group/:groupId/reject-invite', () => {
|
||||
it('clears invitation from user', async () => {
|
||||
await invitedUser.post(`/groups/${party._id}/reject-invite`);
|
||||
|
||||
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id');
|
||||
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.parties[0].id');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -177,13 +177,13 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
});
|
||||
|
||||
it('can remove other invites', async () => {
|
||||
expect(partyInvitedUser.invitations.party).to.not.be.empty;
|
||||
expect(partyInvitedUser.invitations.parties[0]).to.not.be.empty;
|
||||
|
||||
await partyLeader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`);
|
||||
|
||||
let invitedUserWithoutInvite = await partyInvitedUser.get('/user');
|
||||
|
||||
expect(invitedUserWithoutInvite.invitations.party).to.be.empty;
|
||||
expect(invitedUserWithoutInvite.invitations.parties[0]).to.be.empty;
|
||||
});
|
||||
|
||||
it('removes new messages from a member who is removed', async () => {
|
||||
|
||||
@@ -440,7 +440,38 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id);
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
||||
});
|
||||
|
||||
it('allow inviting a user to 2 different parties', async () => {
|
||||
// Create another inviter
|
||||
let inviter2 = await generateUser();
|
||||
|
||||
// Create user to invite
|
||||
let userToInvite = await generateUser();
|
||||
|
||||
// Create second group
|
||||
let party2 = await inviter2.post('/groups', {
|
||||
name: 'Test Party 2',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
// Invite to first party
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
// Invite to second party
|
||||
await inviter2.post(`/groups/${party2._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
// Get updated user
|
||||
let invitedUser = await userToInvite.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.parties.length).to.equal(2);
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(party._id);
|
||||
expect(invitedUser.invitations.parties[1].id).to.equal(party2._id);
|
||||
});
|
||||
|
||||
it('allow inviting a user if party id is not associated with a real party', async () => {
|
||||
@@ -451,7 +482,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id);
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
||||
});
|
||||
|
||||
it('allows 30 members in a party', async () => {
|
||||
|
||||
@@ -45,6 +45,20 @@ describe('PUT /group', () => {
|
||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||
});
|
||||
|
||||
it('updates a group categories', async () => {
|
||||
let categories = [{
|
||||
slug: 'newCat',
|
||||
name: 'New Category',
|
||||
}];
|
||||
|
||||
let updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, {
|
||||
categories,
|
||||
});
|
||||
|
||||
expect(updatedGroup.categories[0].slug).to.eql(categories[0].slug);
|
||||
expect(updatedGroup.categories[0].name).to.eql(categories[0].name);
|
||||
});
|
||||
|
||||
it('allows an admin to update a guild', async () => {
|
||||
let updatedGroup = await adminUser.put(`/groups/${groupToUpdate._id}`, {
|
||||
name: groupUpdatedName,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import moment from 'moment';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
@@ -127,4 +128,106 @@ describe('GET /tasks/user', () => {
|
||||
let allCompletedTodos = await user.get('/tasks/user?type=_allCompletedTodos');
|
||||
expect(allCompletedTodos.length).to.equal(numberOfTodos);
|
||||
});
|
||||
|
||||
it('returns dailies with isDue for the date specified', async () => {
|
||||
// @TODO Add required format
|
||||
let startDate = moment().subtract('1', 'days').toISOString();
|
||||
let createdTasks = await user.post('/tasks/user', [
|
||||
{
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
startDate,
|
||||
frequency: 'daily',
|
||||
everyX: 2,
|
||||
},
|
||||
]);
|
||||
let dailys = await user.get('/tasks/user?type=dailys');
|
||||
|
||||
expect(dailys.length).to.be.at.least(1);
|
||||
expect(dailys[0]._id).to.equal(createdTasks._id);
|
||||
expect(dailys[0].isDue).to.be.false;
|
||||
|
||||
let dailys2 = await user.get(`/tasks/user?type=dailys&dueDate=${startDate}`);
|
||||
expect(dailys2[0]._id).to.equal(createdTasks._id);
|
||||
expect(dailys2[0].isDue).to.be.true;
|
||||
});
|
||||
|
||||
it('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
|
||||
let timezone = 420;
|
||||
await user.update({
|
||||
'preferences.dayStart': 0,
|
||||
'preferences.timezoneOffset': timezone,
|
||||
});
|
||||
let startDate = moment().zone(timezone).subtract('4', 'days').startOf('day').toISOString();
|
||||
await user.post('/tasks/user', [
|
||||
{
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
startDate,
|
||||
frequency: 'daily',
|
||||
everyX: 2,
|
||||
},
|
||||
]);
|
||||
|
||||
let today = moment().format('YYYY-MM-DD');
|
||||
let dailys = await user.get(`/tasks/user?type=dailys&dueDate=${today}`);
|
||||
expect(dailys[0].isDue).to.be.true;
|
||||
|
||||
let yesterday = moment().subtract('1', 'days').format('YYYY-MM-DD');
|
||||
let dailys2 = await user.get(`/tasks/user?type=dailys&dueDate=${yesterday}`);
|
||||
expect(dailys2[0].isDue).to.be.false;
|
||||
});
|
||||
|
||||
|
||||
it('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
|
||||
let timezone = 240;
|
||||
await user.update({
|
||||
'preferences.dayStart': 0,
|
||||
'preferences.timezoneOffset': timezone,
|
||||
});
|
||||
let startDate = moment().zone(timezone).subtract('4', 'days').startOf('day').toISOString();
|
||||
await user.post('/tasks/user', [
|
||||
{
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
startDate,
|
||||
frequency: 'daily',
|
||||
everyX: 2,
|
||||
},
|
||||
]);
|
||||
|
||||
let today = moment().format('YYYY-MM-DD');
|
||||
let dailys = await user.get(`/tasks/user?type=dailys&dueDate=${today}`);
|
||||
expect(dailys[0].isDue).to.be.true;
|
||||
|
||||
let yesterday = moment().subtract('1', 'days').format('YYYY-MM-DD');
|
||||
let dailys2 = await user.get(`/tasks/user?type=dailys&dueDate=${yesterday}`);
|
||||
expect(dailys2[0].isDue).to.be.false;
|
||||
});
|
||||
|
||||
it('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
|
||||
let timezone = 540;
|
||||
await user.update({
|
||||
'preferences.dayStart': 0,
|
||||
'preferences.timezoneOffset': timezone,
|
||||
});
|
||||
let startDate = moment().zone(timezone).subtract('4', 'days').startOf('day').toISOString();
|
||||
await user.post('/tasks/user', [
|
||||
{
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
startDate,
|
||||
frequency: 'daily',
|
||||
everyX: 2,
|
||||
},
|
||||
]);
|
||||
|
||||
let today = moment().format('YYYY-MM-DD');
|
||||
let dailys = await user.get(`/tasks/user?type=dailys&dueDate=${today}`);
|
||||
expect(dailys[0].isDue).to.be.true;
|
||||
|
||||
let yesterday = moment().subtract('1', 'days').format('YYYY-MM-DD');
|
||||
let dailys2 = await user.get(`/tasks/user?type=dailys&dueDate=${yesterday}`);
|
||||
expect(dailys2[0].isDue).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
generateChallenge,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /tasks/unlink-all/:challengeId', () => {
|
||||
let user;
|
||||
let guild;
|
||||
let challenge;
|
||||
let tasksToTest = {
|
||||
habit: {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
up: false,
|
||||
down: true,
|
||||
},
|
||||
todo: {
|
||||
text: 'test todo',
|
||||
type: 'todo',
|
||||
},
|
||||
daily: {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
},
|
||||
reward: {
|
||||
text: 'test reward',
|
||||
type: 'reward',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
});
|
||||
|
||||
it('fails if no keep query', async () => {
|
||||
await expect(user.post(`/tasks/unlink-all/${challenge._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('fails if invalid challenge id', async () => {
|
||||
await expect(user.post('/tasks/unlink-all/123?keep=remove-all'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on an unbroken challenge', async () => {
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
|
||||
await expect(user.post(`/tasks/unlink-all/${challenge._id}?keep=remove-all`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('cantOnlyUnlinkChalTask'),
|
||||
});
|
||||
});
|
||||
|
||||
it('unlinks all tasks from a challenge and deletes them on keep=remove-all', async () => {
|
||||
const daily = await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.habit);
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.reward);
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.todo);
|
||||
await user.del(`/challenges/${challenge._id}`);
|
||||
const response = await user.post(`/tasks/unlink-all/${challenge._id}?keep=remove-all`);
|
||||
expect(response).to.eql({});
|
||||
|
||||
await expect(user.get(`/tasks/${daily._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('unlinks a task from a challenge on keep=keep-all', async () => {
|
||||
const daily = await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
|
||||
const anotherUser = await generateUser();
|
||||
await user.post(`/groups/${guild._id}/invite`, {
|
||||
uuids: [anotherUser._id],
|
||||
});
|
||||
// Have the second user join the group and challenge
|
||||
await anotherUser.post(`/groups/${guild._id}/join`);
|
||||
await anotherUser.post(`/challenges/${challenge._id}/join`);
|
||||
// Have the leader delete the challenge and unlink the tasks
|
||||
await user.del(`/challenges/${challenge._id}`);
|
||||
await user.post(`/tasks/unlink-all/${challenge._id}?keep=keep-all`);
|
||||
// Get the second task for the second user
|
||||
const [, anotherUserTask] = await anotherUser.get('/tasks/user');
|
||||
// Expect the second user to still have the task, but unlinked
|
||||
expect(anotherUserTask.challenge).to.eql({
|
||||
taskId: daily._id,
|
||||
id: challenge._id,
|
||||
shortName: challenge.shortName,
|
||||
broken: 'CHALLENGE_DELETED',
|
||||
winner: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
generateChallenge,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /tasks/unlink-one/:taskId', () => {
|
||||
let user;
|
||||
let guild;
|
||||
let challenge;
|
||||
let tasksToTest = {
|
||||
habit: {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
up: false,
|
||||
down: true,
|
||||
},
|
||||
todo: {
|
||||
text: 'test todo',
|
||||
type: 'todo',
|
||||
},
|
||||
daily: {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
},
|
||||
reward: {
|
||||
text: 'test reward',
|
||||
type: 'reward',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
guild = await generateGroup(user);
|
||||
challenge = await generateChallenge(user, guild);
|
||||
});
|
||||
|
||||
it('fails if no keep query', async () => {
|
||||
const daily = await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
|
||||
await expect(user.post(`/tasks/unlink-one/${daily._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('fails if invalid task id', async () => {
|
||||
await expect(user.post('/tasks/unlink-one/123?keep=remove'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on task not found', async () => {
|
||||
await expect(user.post(`/tasks/unlink-one/${generateUUID()}?keep=keep`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on task unlinked to challenge', async () => {
|
||||
let daily = await user.post('/tasks/user', tasksToTest.daily);
|
||||
await expect(user.post(`/tasks/unlink-one/${daily._id}?keep=keep`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('cantOnlyUnlinkChalTask'),
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on unbroken challenge', async () => {
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
|
||||
let [daily] = await user.get('/tasks/user');
|
||||
await expect(user.post(`/tasks/unlink-one/${daily._id}?keep=keep`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('cantOnlyUnlinkChalTask'),
|
||||
});
|
||||
});
|
||||
|
||||
it('unlinks a task from a challenge and saves it on keep=keep', async () => {
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
|
||||
let [, daily] = await user.get('/tasks/user');
|
||||
await user.del(`/challenges/${challenge._id}`);
|
||||
await user.post(`/tasks/unlink-one/${daily._id}?keep=keep`);
|
||||
[, daily] = await user.get('/tasks/user');
|
||||
expect(daily.challenge).to.eql({});
|
||||
});
|
||||
|
||||
it('unlinks a task from a challenge and deletes it on keep=remove', async () => {
|
||||
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
|
||||
let [, daily] = await user.get('/tasks/user');
|
||||
await user.del(`/challenges/${challenge._id}`);
|
||||
await user.post(`/tasks/unlink-one/${daily._id}?keep=remove`);
|
||||
const tasks = await user.get('/tasks/user');
|
||||
// Only the default task should remain
|
||||
expect(tasks.length).to.eql(1);
|
||||
});
|
||||
});
|
||||
@@ -133,6 +133,7 @@ describe('POST /tasks/user', () => {
|
||||
expect(task.completed).to.equal(false);
|
||||
expect(task.streak).not.to.equal('never');
|
||||
expect(task.value).not.to.equal(324);
|
||||
expect(task.yesterDaily).to.equal(true);
|
||||
});
|
||||
|
||||
it('ignores invalid fields', async () => {
|
||||
|
||||
@@ -396,6 +396,7 @@ describe('PUT /tasks/:id', () => {
|
||||
notes: 'some new notes',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
yesterDaily: false,
|
||||
startDate: moment().add(1, 'days').toDate(),
|
||||
});
|
||||
|
||||
@@ -405,6 +406,7 @@ describe('PUT /tasks/:id', () => {
|
||||
expect(savedDaily.everyX).to.eql(5);
|
||||
expect(savedDaily.isDue).to.be.false;
|
||||
expect(savedDaily.nextDue.length).to.eql(6);
|
||||
expect(savedDaily.yesterDaily).to.be.false;
|
||||
});
|
||||
|
||||
it('can update checklists (replace it)', async () => {
|
||||
|
||||
@@ -18,266 +18,328 @@ import {
|
||||
} from '../../../../../website/server/libs/password';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
|
||||
const DELETE_CONFIRMATION = 'DELETE';
|
||||
|
||||
describe('DELETE /user', () => {
|
||||
let user;
|
||||
let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({balance: 10});
|
||||
});
|
||||
|
||||
it('returns an error if password is wrong', async () => {
|
||||
await expect(user.del('/user', {
|
||||
password: 'wrong-password',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('wrongPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if password is not supplied', async () => {
|
||||
await expect(user.del('/user', {
|
||||
password: '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if excessive feedback is supplied', async () => {
|
||||
let feedbackText = 'spam feedback ';
|
||||
let feedback = feedbackText;
|
||||
while (feedback.length < 10000) {
|
||||
feedback = feedback + feedbackText;
|
||||
}
|
||||
|
||||
await expect(user.del('/user', {
|
||||
password,
|
||||
feedback,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Account deletion feedback is limited to 10,000 characters. For lengthy feedback, email admin@habitica.com.',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if user has active subscription', async () => {
|
||||
let userWithSubscription = await generateUser({'purchased.plan.customerId': 'fake-customer-id'});
|
||||
|
||||
await expect(userWithSubscription.del('/user', {
|
||||
password,
|
||||
})).to.be.rejected.and.to.eventually.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotDeleteActiveAccount'),
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes the user\'s tasks', async () => {
|
||||
// gets the user's tasks ids
|
||||
let ids = [];
|
||||
each(user.tasksOrder, (idsForOrder) => {
|
||||
ids.push(...idsForOrder);
|
||||
});
|
||||
|
||||
expect(ids.length).to.be.above(0); // make sure the user has some task to delete
|
||||
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
await Bluebird.all(map(ids, id => {
|
||||
return expect(checkExistence('tasks', id)).to.eventually.eql(false);
|
||||
}));
|
||||
});
|
||||
|
||||
it('reduces memberCount in challenges user is linked to', async () => {
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
members: 2,
|
||||
});
|
||||
|
||||
let group = populatedGroup.group;
|
||||
let authorizedUser = populatedGroup.members[1];
|
||||
|
||||
let challenge = await generateChallenge(populatedGroup.groupLeader, group);
|
||||
await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await challenge.sync();
|
||||
|
||||
expect(challenge.memberCount).to.eql(2);
|
||||
|
||||
await authorizedUser.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
await challenge.sync();
|
||||
|
||||
expect(challenge.memberCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('deletes the user', async () => {
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
|
||||
it('sends feedback to the admin email', async () => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
|
||||
let feedback = 'Reasons for Deletion';
|
||||
await user.del('/user', {
|
||||
password,
|
||||
feedback,
|
||||
});
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('does not send email if no feedback is supplied', async () => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
expect(email.sendTxn).to.not.be.called;
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('deletes the user with a legacy sha1 password', async () => {
|
||||
let textPassword = 'mySecretPassword';
|
||||
let salt = sha1MakeSalt();
|
||||
let sha1HashedPassword = sha1EncryptPassword(textPassword, salt);
|
||||
|
||||
await user.update({
|
||||
'auth.local.hashed_password': sha1HashedPassword,
|
||||
'auth.local.passwordHashMethod': 'sha1',
|
||||
'auth.local.salt': salt,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
|
||||
expect(user.auth.local.passwordHashMethod).to.equal('sha1');
|
||||
expect(user.auth.local.salt).to.equal(salt);
|
||||
expect(user.auth.local.hashed_password).to.equal(sha1HashedPassword);
|
||||
|
||||
// delete the user
|
||||
await user.del('/user', {
|
||||
password: textPassword,
|
||||
});
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
|
||||
context('last member of a party', () => {
|
||||
let party;
|
||||
|
||||
context('user with local auth', async () => {
|
||||
beforeEach(async () => {
|
||||
party = await generateGroup(user, {
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
user = await generateUser({balance: 10});
|
||||
});
|
||||
|
||||
it('returns an error if password is wrong', async () => {
|
||||
await expect(user.del('/user', {
|
||||
password: 'wrong-password',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('wrongPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes party when user is the only member', async () => {
|
||||
it('returns an error if password is not supplied', async () => {
|
||||
await expect(user.del('/user', {
|
||||
password: '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes the user', async () => {
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
await expect(checkExistence('party', party._id)).to.eventually.eql(false);
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('last member of a private guild', () => {
|
||||
let privateGuild;
|
||||
it('returns an error if excessive feedback is supplied', async () => {
|
||||
let feedbackText = 'spam feedback ';
|
||||
let feedback = feedbackText;
|
||||
while (feedback.length < 10000) {
|
||||
feedback = feedback + feedbackText;
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
privateGuild = await generateGroup(user, {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
await expect(user.del('/user', {
|
||||
password,
|
||||
feedback,
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: 'Account deletion feedback is limited to 10,000 characters. For lengthy feedback, email admin@habitica.com.',
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes guild when user is the only member', async () => {
|
||||
it('returns an error if user has active subscription', async () => {
|
||||
let userWithSubscription = await generateUser({'purchased.plan.customerId': 'fake-customer-id'});
|
||||
|
||||
await expect(userWithSubscription.del('/user', {
|
||||
password,
|
||||
})).to.be.rejected.and.to.eventually.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotDeleteActiveAccount'),
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes the user\'s tasks', async () => {
|
||||
// gets the user's tasks ids
|
||||
let ids = [];
|
||||
each(user.tasksOrder, (idsForOrder) => {
|
||||
ids.push(...idsForOrder);
|
||||
});
|
||||
|
||||
expect(ids.length).to.be.above(0); // make sure the user has some task to delete
|
||||
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
await expect(checkExistence('groups', privateGuild._id)).to.eventually.eql(false);
|
||||
|
||||
await Bluebird.all(map(ids, id => {
|
||||
return expect(checkExistence('tasks', id)).to.eventually.eql(false);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is leader of', () => {
|
||||
let guild, oldLeader, newLeader;
|
||||
|
||||
beforeEach(async () => {
|
||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
it('reduces memberCount in challenges user is linked to', async () => {
|
||||
let populatedGroup = await createAndPopulateGroup({
|
||||
members: 2,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
newLeader = members[0];
|
||||
oldLeader = groupLeader;
|
||||
});
|
||||
let group = populatedGroup.group;
|
||||
let authorizedUser = populatedGroup.members[1];
|
||||
|
||||
it('chooses new group leader for any group user was the leader of', async () => {
|
||||
await oldLeader.del('/user', {
|
||||
let challenge = await generateChallenge(populatedGroup.groupLeader, group);
|
||||
await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||
|
||||
await challenge.sync();
|
||||
|
||||
expect(challenge.memberCount).to.eql(2);
|
||||
|
||||
await authorizedUser.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
let updatedGuild = await newLeader.get(`/groups/${guild._id}`);
|
||||
await challenge.sync();
|
||||
|
||||
expect(updatedGuild.leader).to.exist;
|
||||
expect(updatedGuild.leader._id).to.not.eql(oldLeader._id);
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is a part of', () => {
|
||||
let group1, group2, userToDelete, otherUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
userToDelete = await generateUser({balance: 10});
|
||||
|
||||
group1 = await generateGroup(userToDelete, {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
let {group, members} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 3,
|
||||
});
|
||||
|
||||
group2 = group;
|
||||
otherUser = members[0];
|
||||
|
||||
await userToDelete.post(`/groups/${group2._id}/join`);
|
||||
expect(challenge.memberCount).to.eql(1);
|
||||
});
|
||||
|
||||
it('removes user from all groups user was a part of', async () => {
|
||||
await userToDelete.del('/user', {
|
||||
it('sends feedback to the admin email', async () => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
|
||||
let feedback = 'Reasons for Deletion';
|
||||
await user.del('/user', {
|
||||
password,
|
||||
feedback,
|
||||
});
|
||||
|
||||
expect(email.sendTxn).to.be.calledOnce;
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('does not send email if no feedback is supplied', async () => {
|
||||
sandbox.spy(email, 'sendTxn');
|
||||
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
let updatedGroup1Members = await otherUser.get(`/groups/${group1._id}/members`);
|
||||
let updatedGroup2Members = await otherUser.get(`/groups/${group2._id}/members`);
|
||||
let userInGroup = find(updatedGroup2Members, (member) => {
|
||||
return member._id === userToDelete._id;
|
||||
expect(email.sendTxn).to.not.be.called;
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('deletes the user with a legacy sha1 password', async () => {
|
||||
let textPassword = 'mySecretPassword';
|
||||
let salt = sha1MakeSalt();
|
||||
let sha1HashedPassword = sha1EncryptPassword(textPassword, salt);
|
||||
|
||||
await user.update({
|
||||
'auth.local.hashed_password': sha1HashedPassword,
|
||||
'auth.local.passwordHashMethod': 'sha1',
|
||||
'auth.local.salt': salt,
|
||||
});
|
||||
|
||||
expect(updatedGroup1Members).to.be.empty;
|
||||
expect(updatedGroup2Members).to.not.be.empty;
|
||||
expect(userInGroup).to.not.exist;
|
||||
await user.sync();
|
||||
|
||||
expect(user.auth.local.passwordHashMethod).to.equal('sha1');
|
||||
expect(user.auth.local.salt).to.equal(salt);
|
||||
expect(user.auth.local.hashed_password).to.equal(sha1HashedPassword);
|
||||
|
||||
// delete the user
|
||||
await user.del('/user', {
|
||||
password: textPassword,
|
||||
});
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
|
||||
context('last member of a party', () => {
|
||||
let party;
|
||||
|
||||
beforeEach(async () => {
|
||||
party = await generateGroup(user, {
|
||||
type: 'party',
|
||||
privacy: 'private',
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes party when user is the only member', async () => {
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
await expect(checkExistence('party', party._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('last member of a private guild', () => {
|
||||
let privateGuild;
|
||||
|
||||
beforeEach(async () => {
|
||||
privateGuild = await generateGroup(user, {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes guild when user is the only member', async () => {
|
||||
await user.del('/user', {
|
||||
password,
|
||||
});
|
||||
await expect(checkExistence('groups', privateGuild._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is leader of', () => {
|
||||
let guild, oldLeader, newLeader;
|
||||
|
||||
beforeEach(async () => {
|
||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
newLeader = members[0];
|
||||
oldLeader = groupLeader;
|
||||
});
|
||||
|
||||
it('chooses new group leader for any group user was the leader of', async () => {
|
||||
await oldLeader.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
let updatedGuild = await newLeader.get(`/groups/${guild._id}`);
|
||||
|
||||
expect(updatedGuild.leader).to.exist;
|
||||
expect(updatedGuild.leader._id).to.not.eql(oldLeader._id);
|
||||
});
|
||||
});
|
||||
|
||||
context('groups user is a part of', () => {
|
||||
let group1, group2, userToDelete, otherUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
userToDelete = await generateUser({balance: 10});
|
||||
|
||||
group1 = await generateGroup(userToDelete, {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
});
|
||||
|
||||
let {group, members} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 3,
|
||||
});
|
||||
|
||||
group2 = group;
|
||||
otherUser = members[0];
|
||||
|
||||
await userToDelete.post(`/groups/${group2._id}/join`);
|
||||
});
|
||||
|
||||
it('removes user from all groups user was a part of', async () => {
|
||||
await userToDelete.del('/user', {
|
||||
password,
|
||||
});
|
||||
|
||||
let updatedGroup1Members = await otherUser.get(`/groups/${group1._id}/members`);
|
||||
let updatedGroup2Members = await otherUser.get(`/groups/${group2._id}/members`);
|
||||
let userInGroup = find(updatedGroup2Members, (member) => {
|
||||
return member._id === userToDelete._id;
|
||||
});
|
||||
|
||||
expect(updatedGroup1Members).to.be.empty;
|
||||
expect(updatedGroup2Members).to.not.be.empty;
|
||||
expect(userInGroup).to.not.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('user with Facebook auth', async () => {
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
auth: {
|
||||
facebook: {
|
||||
id: 'facebook-id',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if confirmation phrase is wrong', async () => {
|
||||
await expect(user.del('/user', {
|
||||
password: 'just-do-it',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('incorrectDeletePhrase'),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error if confirmation phrase is not supplied', async () => {
|
||||
await expect(user.del('/user', {
|
||||
password: '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes a Facebook user', async () => {
|
||||
await user.del('/user', {
|
||||
password: DELETE_CONFIRMATION,
|
||||
});
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('user with Google auth', async () => {
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
auth: {
|
||||
google: {
|
||||
id: 'google-id',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes a Google user', async () => {
|
||||
await user.del('/user', {
|
||||
password: DELETE_CONFIRMATION,
|
||||
});
|
||||
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
generateUser,
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
|
||||
@@ -31,4 +32,70 @@ describe('POST /user/purchase/:type/:key', () => {
|
||||
|
||||
expect(user.items[type][key]).to.equal(1);
|
||||
});
|
||||
|
||||
it('can convert gold to gems if subscribed', async () => {
|
||||
let oldBalance = user.balance;
|
||||
await user.update({
|
||||
'purchased.plan.customerId': 'group-plan',
|
||||
'stats.gp': 1000,
|
||||
});
|
||||
await user.post('/user/purchase/gems/gem');
|
||||
await user.sync();
|
||||
expect(user.balance).to.equal(oldBalance + 0.25);
|
||||
});
|
||||
|
||||
it('leader can convert gold to gems even if the group plan prevents it', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
});
|
||||
await group.update({
|
||||
'leaderOnly.getGems': true,
|
||||
'purchased.plan.customerId': 123,
|
||||
});
|
||||
await groupLeader.sync();
|
||||
let oldBalance = groupLeader.balance;
|
||||
|
||||
await groupLeader.update({
|
||||
'purchased.plan.customerId': 'group-plan',
|
||||
'stats.gp': 1000,
|
||||
});
|
||||
await groupLeader.post('/user/purchase/gems/gem');
|
||||
|
||||
await groupLeader.sync();
|
||||
expect(groupLeader.balance).to.equal(oldBalance + 0.25);
|
||||
});
|
||||
|
||||
it('cannot convert gold to gems if the group plan prevents it', async () => {
|
||||
let { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'test',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
await group.update({
|
||||
'leaderOnly.getGems': true,
|
||||
'purchased.plan.customerId': 123,
|
||||
});
|
||||
let oldBalance = members[0].balance;
|
||||
|
||||
await members[0].update({
|
||||
'purchased.plan.customerId': 'group-plan',
|
||||
'stats.gp': 1000,
|
||||
});
|
||||
await expect(members[0].post('/user/purchase/gems/gem'))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('groupPolicyCannotGetGems'),
|
||||
});
|
||||
|
||||
await members[0].sync();
|
||||
expect(members[0].balance).to.equal(oldBalance);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,17 +2,34 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import content from '../../../../../website/common/script/content/index';
|
||||
|
||||
describe('POST /user/release-both', () => {
|
||||
let user;
|
||||
let animal = 'Wolf-Base';
|
||||
const loadPets = () => {
|
||||
let pets = {};
|
||||
for (let p in content.pets) {
|
||||
pets[p] = content.pets[p];
|
||||
pets[p] = 5;
|
||||
}
|
||||
return pets;
|
||||
};
|
||||
const loadMounts = () => {
|
||||
let mounts = {};
|
||||
for (let m in content.pets) {
|
||||
mounts[m] = content.pets[m];
|
||||
mounts[m] = true;
|
||||
}
|
||||
return mounts;
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
'items.currentMount': animal,
|
||||
'items.currentPet': animal,
|
||||
'items.pets': {animal: 5},
|
||||
'items.mounts': {animal: true},
|
||||
'items.pets': loadPets(),
|
||||
'items.mounts': loadMounts(),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,15 +2,25 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import content from '../../../../../website/common/script/content/index';
|
||||
|
||||
describe('POST /user/release-mounts', () => {
|
||||
let user;
|
||||
let animal = 'Wolf-Base';
|
||||
|
||||
const loadMounts = () => {
|
||||
let mounts = {};
|
||||
for (let m in content.pets) {
|
||||
mounts[m] = content.pets[m];
|
||||
mounts[m] = true;
|
||||
}
|
||||
return mounts;
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
'items.currentMount': animal,
|
||||
'items.mounts': {animal: true},
|
||||
'items.mounts': loadMounts(),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,15 +2,25 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import content from '../../../../../website/common/script/content/index';
|
||||
|
||||
describe('POST /user/release-pets', () => {
|
||||
let user;
|
||||
let animal = 'Wolf-Base';
|
||||
|
||||
const loadPets = () => {
|
||||
let pets = {};
|
||||
for (let p in content.pets) {
|
||||
pets[p] = content.pets[p];
|
||||
pets[p] = 5;
|
||||
}
|
||||
return pets;
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
'items.currentPet': animal,
|
||||
'items.pets': {animal: 5},
|
||||
'items.pets': loadPets(),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -509,7 +509,73 @@ describe('POST /user/auth/local/register', () => {
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.party).to.eql({
|
||||
expect(user.invitations.parties[0].id).to.eql(group._id);
|
||||
expect(user.invitations.parties[0].name).to.eql(group.name);
|
||||
expect(user.invitations.parties[0].inviter).to.eql(groupLeader._id);
|
||||
});
|
||||
|
||||
it('awards achievement to inviter', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now(),
|
||||
}));
|
||||
|
||||
await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
await groupLeader.sync();
|
||||
expect(groupLeader.achievements.invitedFriend).to.be.true;
|
||||
});
|
||||
|
||||
it('user not added to a party on expired invite', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now() - 6.912e8, // 8 days old
|
||||
}));
|
||||
|
||||
let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.party).to.eql({});
|
||||
});
|
||||
|
||||
it('adds a user to a guild on an invite of type other than party', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'guild', privacy: 'private' },
|
||||
});
|
||||
|
||||
let invite = encrypt(JSON.stringify({
|
||||
id: group._id,
|
||||
inviter: groupLeader._id,
|
||||
sentAt: Date.now(),
|
||||
}));
|
||||
|
||||
let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.guilds[0]).to.eql({
|
||||
id: group._id,
|
||||
name: group.name,
|
||||
inviter: groupLeader._id,
|
||||
|
||||
@@ -102,6 +102,7 @@ describe('Amazon Payments', () => {
|
||||
});
|
||||
|
||||
it('should purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
await amzLib.checkout({user, orderReferenceId, headers});
|
||||
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
@@ -111,22 +112,52 @@ describe('Amazon Payments', () => {
|
||||
headers,
|
||||
});
|
||||
expectAmazonStubs();
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('should gift gems', async () => {
|
||||
it('should error if gem amount is too low', async () => {
|
||||
let receivingUser = new User();
|
||||
receivingUser.save();
|
||||
let gift = {
|
||||
type: 'gems',
|
||||
gems: {
|
||||
amount: 16,
|
||||
amount: 0,
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(amzLib.checkout({gift, user, orderReferenceId, headers}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
message: 'Amount must be at least 1.',
|
||||
name: 'BadRequest',
|
||||
});
|
||||
});
|
||||
|
||||
it('should error if user cannot get gems gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
await expect(amzLib.checkout({user, orderReferenceId, headers})).to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
name: 'NotAuthorized',
|
||||
});
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('should gift gems', async () => {
|
||||
let receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
let gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 16,
|
||||
},
|
||||
};
|
||||
amount = 16 / 4;
|
||||
await amzLib.checkout({gift, user, orderReferenceId, headers});
|
||||
|
||||
gift.member = receivingUser;
|
||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||
user,
|
||||
|
||||
@@ -57,7 +57,20 @@ describe('Apple Payments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if the user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
});
|
||||
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('purchases gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
await applePayments.verifyGemPurchase(user, receipt, headers);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
@@ -74,6 +87,8 @@ describe('Apple Payments', () => {
|
||||
amount: 5.25,
|
||||
headers,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -79,6 +79,13 @@ describe('cron', () => {
|
||||
expect(user.purchased.plan.gemsBought).to.equal(0);
|
||||
});
|
||||
|
||||
it('resets plan.gemsBought on a new month if user does not have purchased.plan.dateUpdated', () => {
|
||||
user.purchased.plan.gemsBought = 10;
|
||||
user.purchased.plan.dateUpdated = undefined;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.purchased.plan.gemsBought).to.equal(0);
|
||||
});
|
||||
|
||||
it('does not reset plan.gemsBought within the month', () => {
|
||||
let clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').unix());
|
||||
user.purchased.plan.dateUpdated = moment().startOf('month').toDate();
|
||||
@@ -476,6 +483,25 @@ describe('cron', () => {
|
||||
|
||||
expect(progress.down).to.equal(-1);
|
||||
});
|
||||
|
||||
it('should do damage for only yesterday\'s dailies', () => {
|
||||
daysMissed = 3;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||
|
||||
let daily = {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
};
|
||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
||||
tasksByType.dailys.push(task);
|
||||
tasksByType.dailys[1].startDate = moment(new Date()).subtract({days: 2});
|
||||
tasksByType.dailys[1].everyX = 2;
|
||||
tasksByType.dailys[1].frequency = 'daily';
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(user.stats.hp).to.equal(48);
|
||||
});
|
||||
});
|
||||
|
||||
describe('habits', () => {
|
||||
@@ -559,6 +585,114 @@ describe('cron', () => {
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should reset a weekly habit counter with custom daily start', () => {
|
||||
clock.restore();
|
||||
|
||||
// Server clock: Monday 12am UTC
|
||||
let monday = new Date('May 22, 2017 00:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(monday);
|
||||
|
||||
// cron runs at 2am
|
||||
user.preferences.dayStart = 2;
|
||||
|
||||
tasksByType.habits[0].frequency = 'weekly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
daysMissed = 1;
|
||||
|
||||
// should not reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(1);
|
||||
|
||||
clock.restore();
|
||||
|
||||
// Server clock: Monday 3am UTC
|
||||
monday = new Date('May 22, 2017 03:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(monday);
|
||||
|
||||
// should reset after user CDS
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should not reset a weekly habit counter when server tz is Monday but user\'s tz is Tuesday', () => {
|
||||
clock.restore();
|
||||
|
||||
// Server clock: Monday 11pm UTC
|
||||
let monday = new Date('May 22, 2017 23:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(monday);
|
||||
|
||||
// User clock: Tuesday 1am UTC + 2
|
||||
user.preferences.timezoneOffset = -120;
|
||||
|
||||
tasksByType.habits[0].frequency = 'weekly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
daysMissed = 1;
|
||||
|
||||
// should not reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(1);
|
||||
|
||||
// User missed one cron, which will subtract User clock back to Monday 1am UTC + 2
|
||||
// should reset
|
||||
daysMissed = 2;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should reset a weekly habit counter when server tz is Sunday but user\'s tz is Monday', () => {
|
||||
clock.restore();
|
||||
|
||||
// Server clock: Sunday 11pm UTC
|
||||
let sunday = new Date('May 21, 2017 23:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(sunday);
|
||||
|
||||
// User clock: Monday 2am UTC + 3
|
||||
user.preferences.timezoneOffset = -180;
|
||||
|
||||
tasksByType.habits[0].frequency = 'weekly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
daysMissed = 1;
|
||||
|
||||
// should reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should not reset a weekly habit counter when server tz is Monday but user\'s tz is Sunday', () => {
|
||||
clock.restore();
|
||||
|
||||
// Server clock: Monday 2am UTC
|
||||
let monday = new Date('May 22, 2017 02:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(monday);
|
||||
|
||||
// User clock: Sunday 11pm UTC - 3
|
||||
user.preferences.timezoneOffset = 180;
|
||||
|
||||
tasksByType.habits[0].frequency = 'weekly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
daysMissed = 1;
|
||||
|
||||
// should not reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(1);
|
||||
});
|
||||
|
||||
it('should reset a monthly habit counter the first day of each month', () => {
|
||||
tasksByType.habits[0].frequency = 'monthly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
@@ -577,6 +711,59 @@ describe('cron', () => {
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should reset a monthly habit counter when server tz is last day of month but user tz is first day of the month', () => {
|
||||
clock.restore();
|
||||
daysMissed = 0;
|
||||
|
||||
// Server clock: 4/30/17 11pm UTC
|
||||
let monday = new Date('April 30, 2017 23:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(monday);
|
||||
|
||||
// User clock: 5/1/17 2am UTC + 3
|
||||
user.preferences.timezoneOffset = -180;
|
||||
|
||||
tasksByType.habits[0].frequency = 'monthly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
daysMissed = 1;
|
||||
|
||||
// should reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
|
||||
it('should not reset a monthly habit counter when server tz is first day of month but user tz is 2nd day of the month', () => {
|
||||
clock.restore();
|
||||
|
||||
// Server clock: 5/1/17 11pm UTC
|
||||
let monday = new Date('May 1, 2017 23:00:00 GMT').getTime();
|
||||
clock = sinon.useFakeTimers(monday);
|
||||
|
||||
// User clock: 5/2/17 2am UTC + 3
|
||||
user.preferences.timezoneOffset = -180;
|
||||
|
||||
tasksByType.habits[0].frequency = 'monthly';
|
||||
tasksByType.habits[0].counterUp = 1;
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
daysMissed = 1;
|
||||
|
||||
// should not reset
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(1);
|
||||
|
||||
// User missed one day, which will subtract User clock back to 5/1/17 2am UTC + 3
|
||||
// should reset
|
||||
daysMissed = 2;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -63,7 +63,21 @@ describe('Google Payments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
|
||||
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
name: 'NotAuthorized',
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
});
|
||||
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('purchases gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
|
||||
|
||||
expect(iapSetupStub).to.be.calledOnce;
|
||||
@@ -82,6 +96,8 @@ describe('Google Payments', () => {
|
||||
amount: 5.25,
|
||||
headers,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -596,7 +596,7 @@ describe('Purchasing a group plan for group', () => {
|
||||
|
||||
let updatedUser = await User.findById(recipient._id).exec();
|
||||
|
||||
expect(updatedUser.purchased.plan.extraMonths).to.within(7, 8);
|
||||
expect(updatedUser.purchased.plan.extraMonths).to.within(7, 9);
|
||||
});
|
||||
|
||||
it('adds months to members with existing recurring subscription and ignores existing negative extraMonths', async () => {
|
||||
|
||||
@@ -61,23 +61,54 @@ describe('Paypal Payments', () => {
|
||||
});
|
||||
|
||||
it('creates a link for gem purchases', async () => {
|
||||
let link = await paypalPayments.checkout();
|
||||
let link = await paypalPayments.checkout({user: new User()});
|
||||
|
||||
expect(paypalPaymentCreateStub).to.be.calledOnce;
|
||||
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems', 5.00));
|
||||
expect(link).to.eql(approvalHerf);
|
||||
});
|
||||
|
||||
it('creates a link for gifting gems', async () => {
|
||||
it('should error if gem amount is too low', async () => {
|
||||
let receivingUser = new User();
|
||||
receivingUser.save();
|
||||
let gift = {
|
||||
type: 'gems',
|
||||
gems: {
|
||||
amount: 16,
|
||||
amount: 0,
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(paypalPayments.checkout({gift}))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
message: 'Amount must be at least 1.',
|
||||
name: 'BadRequest',
|
||||
});
|
||||
});
|
||||
|
||||
it('should error if the user cannot get gems', async () => {
|
||||
let user = new User();
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
|
||||
await expect(paypalPayments.checkout({user})).to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
name: 'NotAuthorized',
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a link for gifting gems', async () => {
|
||||
let receivingUser = new User();
|
||||
await receivingUser.save();
|
||||
let gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 16,
|
||||
},
|
||||
};
|
||||
|
||||
let link = await paypalPayments.checkout({gift});
|
||||
|
||||
expect(paypalPaymentCreateStub).to.be.calledOnce;
|
||||
|
||||
@@ -48,7 +48,57 @@ describe('Stripe Payments', () => {
|
||||
payments.createSubscription.restore();
|
||||
});
|
||||
|
||||
it('should error if gem amount is too low', async () => {
|
||||
let receivingUser = new User();
|
||||
receivingUser.save();
|
||||
gift = {
|
||||
type: 'gems',
|
||||
gems: {
|
||||
amount: 0,
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 400,
|
||||
message: 'Amount must be at least 1.',
|
||||
name: 'BadRequest',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should error if user cannot get gems', async () => {
|
||||
gift = undefined;
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||
|
||||
await expect(stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
gift,
|
||||
groupId,
|
||||
email,
|
||||
headers,
|
||||
coupon,
|
||||
}, stripe)).to.eventually.be.rejected.and.to.eql({
|
||||
httpCode: 401,
|
||||
message: i18n.t('groupPolicyCannotGetGems'),
|
||||
name: 'NotAuthorized',
|
||||
});
|
||||
});
|
||||
|
||||
it('should purchase gems', async () => {
|
||||
gift = undefined;
|
||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||
|
||||
await stripePayments.checkout({
|
||||
token,
|
||||
user,
|
||||
@@ -73,16 +123,18 @@ describe('Stripe Payments', () => {
|
||||
paymentMethod: 'Stripe',
|
||||
gift,
|
||||
});
|
||||
expect(user.canGetGems).to.be.calledOnce;
|
||||
user.canGetGems.restore();
|
||||
});
|
||||
|
||||
it('should gift gems', async () => {
|
||||
let receivingUser = new User();
|
||||
receivingUser.save();
|
||||
await receivingUser.save();
|
||||
gift = {
|
||||
type: 'gems',
|
||||
uuid: receivingUser._id,
|
||||
gems: {
|
||||
amount: 16,
|
||||
uuid: receivingUser._id,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -96,7 +148,6 @@ describe('Stripe Payments', () => {
|
||||
coupon,
|
||||
}, stripe);
|
||||
|
||||
gift.member = receivingUser;
|
||||
expect(stripeChargeStub).to.be.calledOnce;
|
||||
expect(stripeChargeStub).to.be.calledWith({
|
||||
amount: '400',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Bluebird from 'bluebird';
|
||||
import moment from 'moment';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../website/server/models/group';
|
||||
import common from '../../../../../website/common';
|
||||
|
||||
describe('User Model', () => {
|
||||
@@ -179,6 +180,75 @@ describe('User Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('canGetGems', () => {
|
||||
let user;
|
||||
let group;
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
let leader = new User();
|
||||
group = new Group({
|
||||
name: 'test',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
leader: leader._id,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns true if user is not subscribed', async () => {
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is not subscribed with a group plan', async () => {
|
||||
user.purchased.plan.customerId = 123;
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is subscribed with a group plan', async () => {
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is part of a group', async () => {
|
||||
user.guilds.push(group._id);
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is part of a group with a subscription', async () => {
|
||||
user.guilds.push(group._id);
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
group.purchased.plan.customerId = 123;
|
||||
await group.save();
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if leader is part of a group with a subscription and canGetGems: false', async () => {
|
||||
user.guilds.push(group._id);
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
group.purchased.plan.customerId = 123;
|
||||
group.leader = user._id;
|
||||
group.leaderOnly.getGems = true;
|
||||
await group.save();
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is part of a group with no subscription but canGetGems: false', async () => {
|
||||
user.guilds.push(group._id);
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
group.leaderOnly.getGems = true;
|
||||
await group.save();
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false if user is part of a group with a subscription and canGetGems: false', async () => {
|
||||
user.guilds.push(group._id);
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
group.purchased.plan.customerId = 123;
|
||||
group.leaderOnly.getGems = true;
|
||||
await group.save();
|
||||
expect(await user.canGetGems()).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('hasNotCancelled', () => {
|
||||
let user;
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,63 +1,63 @@
|
||||
describe('Group Tasks Meta Actions Controller', () => {
|
||||
let rootScope, scope, user, userSerivce;
|
||||
|
||||
beforeEach(() => {
|
||||
module(function($provide) {
|
||||
$provide.value('User', {});
|
||||
});
|
||||
|
||||
inject(($rootScope, $controller) => {
|
||||
rootScope = $rootScope;
|
||||
|
||||
user = specHelper.newUser();
|
||||
user._id = "unique-user-id";
|
||||
userSerivce = {user: user};
|
||||
|
||||
scope = $rootScope.$new();
|
||||
|
||||
scope.task = {
|
||||
group: {
|
||||
assignedUsers: [],
|
||||
approval: {
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
};
|
||||
scope.task._edit = angular.copy(scope.task);
|
||||
|
||||
$controller('GroupTaskActionsCtrl', {$scope: scope, User: userSerivce});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleTaskRequiresApproval', function () {
|
||||
it('toggles task approval required field from false to true', function () {
|
||||
scope.toggleTaskRequiresApproval();
|
||||
expect(scope.task._edit.group.approval.required).to.be.true;
|
||||
});
|
||||
|
||||
it('toggles task approval required field from true to false', function () {
|
||||
scope.task._edit.group.approval.required = true;
|
||||
scope.toggleTaskRequiresApproval();
|
||||
expect(scope.task._edit.group.approval.required).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('assign events', function () {
|
||||
it('adds a group member to assigned users on "addedGroupMember" event ', () => {
|
||||
var testId = 'test-id';
|
||||
rootScope.$broadcast('addedGroupMember', testId);
|
||||
expect(scope.task.group.assignedUsers).to.contain(testId);
|
||||
expect(scope.task._edit.group.assignedUsers).to.contain(testId);
|
||||
});
|
||||
|
||||
it('removes a group member to assigned users on "addedGroupMember" event ', () => {
|
||||
var testId = 'test-id';
|
||||
scope.task.group.assignedUsers.push(testId);
|
||||
scope.task._edit.group.assignedUsers.push(testId);
|
||||
rootScope.$broadcast('removedGroupMember', testId);
|
||||
expect(scope.task.group.assignedUsers).to.not.contain(testId);
|
||||
expect(scope.task._edit.group.assignedUsers).to.not.contain(testId);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Group Tasks Meta Actions Controller', () => {
|
||||
let rootScope, scope, user, userSerivce;
|
||||
|
||||
beforeEach(() => {
|
||||
module(function($provide) {
|
||||
$provide.value('User', {});
|
||||
});
|
||||
|
||||
inject(($rootScope, $controller) => {
|
||||
rootScope = $rootScope;
|
||||
|
||||
user = specHelper.newUser();
|
||||
user._id = "unique-user-id";
|
||||
userSerivce = {user: user};
|
||||
|
||||
scope = $rootScope.$new();
|
||||
|
||||
scope.task = {
|
||||
group: {
|
||||
assignedUsers: [],
|
||||
approval: {
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
};
|
||||
scope.task._edit = angular.copy(scope.task);
|
||||
|
||||
$controller('GroupTaskActionsCtrl', {$scope: scope, User: userSerivce});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleTaskRequiresApproval', function () {
|
||||
it('toggles task approval required field from false to true', function () {
|
||||
scope.toggleTaskRequiresApproval();
|
||||
expect(scope.task._edit.group.approval.required).to.be.true;
|
||||
});
|
||||
|
||||
it('toggles task approval required field from true to false', function () {
|
||||
scope.task._edit.group.approval.required = true;
|
||||
scope.toggleTaskRequiresApproval();
|
||||
expect(scope.task._edit.group.approval.required).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('assign events', function () {
|
||||
it('adds a group member to assigned users on "addedGroupMember" event ', () => {
|
||||
var testId = 'test-id';
|
||||
rootScope.$broadcast('addedGroupMember', testId);
|
||||
expect(scope.task.group.assignedUsers).to.contain(testId);
|
||||
expect(scope.task._edit.group.assignedUsers).to.contain(testId);
|
||||
});
|
||||
|
||||
it('removes a group member to assigned users on "addedGroupMember" event ', () => {
|
||||
var testId = 'test-id';
|
||||
scope.task.group.assignedUsers.push(testId);
|
||||
scope.task._edit.group.assignedUsers.push(testId);
|
||||
rootScope.$broadcast('removedGroupMember', testId);
|
||||
expect(scope.task.group.assignedUsers).to.not.contain(testId);
|
||||
expect(scope.task._edit.group.assignedUsers).to.not.contain(testId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ describe('Notification Controller', function() {
|
||||
beforeEach(function() {
|
||||
user = specHelper.newUser();
|
||||
user._id = "unique-user-id";
|
||||
user.needsCron = false;
|
||||
|
||||
var userSync = sinon.stub().returns({
|
||||
then: function then (f) { f(); }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Vue from 'vue';
|
||||
import DrawerComponent from 'client/components/inventory/drawer.vue';
|
||||
import DrawerComponent from 'client/components/ui/drawer.vue';
|
||||
|
||||
describe('DrawerComponent', () => {
|
||||
it('sets the correct default data', () => {
|
||||
@@ -9,7 +9,7 @@ describe('tasks actions', () => {
|
||||
});
|
||||
|
||||
describe('fetchUserTasks', () => {
|
||||
it('fetches user tasks', async () => {
|
||||
xit('fetches user tasks', async () => {
|
||||
expect(store.state.tasks.loadingStatus).to.equal('NOT_LOADED');
|
||||
const tasks = [{_id: 1}];
|
||||
sandbox.stub(axios, 'get').withArgs('/api/v3/tasks/user').returns(Promise.resolve({data: {data: tasks}}));
|
||||
@@ -36,7 +36,7 @@ describe('tasks actions', () => {
|
||||
expect(store.state.tasks.loadingStatus).to.equal('LOADED');
|
||||
});
|
||||
|
||||
it('can reload tasks if forceLoad is true', async () => {
|
||||
xit('can reload tasks if forceLoad is true', async () => {
|
||||
store.state.tasks = {
|
||||
loadingStatus: 'LOADED',
|
||||
data: [{_id: 1}],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import generateStore from 'client/store';
|
||||
|
||||
describe('tasks actions', () => {
|
||||
describe('user actions', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -122,7 +122,7 @@ describe('achievements', () => {
|
||||
});
|
||||
|
||||
it('card achievements exist with counts', () => {
|
||||
let cardTypes = ['greeting', 'thankyou', 'birthday', 'congrats', 'getwell'];
|
||||
let cardTypes = ['greeting', 'thankyou', 'birthday', 'congrats', 'getwell', 'goodluck'];
|
||||
cardTypes.forEach((card) => {
|
||||
let cardAchiev = basicAchievs[`${card}Cards`];
|
||||
|
||||
|
||||
@@ -76,5 +76,35 @@ describe('shared.ops.buyHealthPotion', () => {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not allow potion purchases when hp is zero', (done) => {
|
||||
user.stats.hp = 0;
|
||||
user.stats.gp = 40;
|
||||
try {
|
||||
buyHealthPotion(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('messageHealthAlreadyMin'));
|
||||
expect(user.stats.hp).to.eql(0);
|
||||
expect(user.stats.gp).to.eql(40);
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not allow potion purchases when hp is negative', (done) => {
|
||||
user.stats.hp = -8;
|
||||
user.stats.gp = 40;
|
||||
try {
|
||||
buyHealthPotion(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('messageHealthAlreadyMin'));
|
||||
expect(user.stats.hp).to.eql(-8);
|
||||
expect(user.stats.gp).to.eql(40);
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
import changeClass from '../../../website/common/script/ops/changeClass';
|
||||
import {
|
||||
NotAuthorized,
|
||||
@@ -58,7 +60,8 @@ describe('shared.ops.changeClass', () => {
|
||||
|
||||
it('changes class', () => {
|
||||
user.stats.class = 'healer';
|
||||
user.items.gear.owned.armor_rogue_1 = true; // eslint-disable-line camelcase
|
||||
user.items.gear.owned.weapon_healer_3 = true;
|
||||
user.items.gear.equipped.weapon = 'weapon_healer_3';
|
||||
|
||||
let [data] = changeClass(user, {query: {class: 'rogue'}});
|
||||
expect(data).to.eql({
|
||||
@@ -70,13 +73,10 @@ describe('shared.ops.changeClass', () => {
|
||||
|
||||
expect(user.stats.class).to.equal('rogue');
|
||||
expect(user.flags.classSelected).to.be.true;
|
||||
expect(user.items.gear.equipped.weapon).to.equal('weapon_rogue_0');
|
||||
expect(user.items.gear.owned.weapon_rogue_0).to.be.true;
|
||||
expect(user.items.gear.equipped.armor).to.equal('armor_rogue_1');
|
||||
expect(user.items.gear.owned.armor_rogue_1).to.be.true;
|
||||
expect(user.items.gear.equipped.shield).to.equal('shield_rogue_0');
|
||||
expect(user.items.gear.owned.shield_rogue_0).to.be.true;
|
||||
expect(user.items.gear.equipped.head).to.equal('head_base_0');
|
||||
expect(user.items.gear.owned.weapon_healer_3).to.be.true;
|
||||
expect(user.items.gear.equipped.weapon).to.equal('weapon_healer_3');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -14,10 +14,18 @@ describe('shared.ops.releaseBoth', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
for (let p in content.pets) {
|
||||
user.items.pets[p] = content.pets[p];
|
||||
user.items.pets[p] = 5;
|
||||
}
|
||||
|
||||
for (let m in content.pets) {
|
||||
user.items.mounts[m] = content.pets[m];
|
||||
user.items.mounts[m] = true;
|
||||
}
|
||||
|
||||
user.items.currentMount = animal;
|
||||
user.items.currentPet = animal;
|
||||
user.items.pets[animal] = 5;
|
||||
user.items.mounts[animal] = true;
|
||||
user.balance = 1.5;
|
||||
});
|
||||
|
||||
@@ -34,7 +42,7 @@ describe('shared.ops.releaseBoth', () => {
|
||||
});
|
||||
|
||||
it('grants triad bingo with gems', () => {
|
||||
let [, message] = releaseBoth(user);
|
||||
let message = releaseBoth(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('mountsAndPetsReleased'));
|
||||
expect(user.achievements.triadBingoCount).to.equal(1);
|
||||
@@ -45,27 +53,79 @@ describe('shared.ops.releaseBoth', () => {
|
||||
user.achievements.triadBingo = 1;
|
||||
user.achievements.triadBingoCount = 1;
|
||||
|
||||
let [, message] = releaseBoth(user);
|
||||
let message = releaseBoth(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('mountsAndPetsReleased'));
|
||||
expect(user.achievements.triadBingoCount).to.equal(2);
|
||||
});
|
||||
|
||||
it('does not grant triad bingo if any pet has not been previously found', () => {
|
||||
let triadBingoCountBeforeRelease = user.achievements.triadBingoCount;
|
||||
user.items.pets[animal] = -1;
|
||||
let message = releaseBoth(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('mountsAndPetsReleased'));
|
||||
expect(user.achievements.triadBingoCount).to.equal(triadBingoCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('releases pets', () => {
|
||||
let [, message] = releaseBoth(user);
|
||||
let message = releaseBoth(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('mountsAndPetsReleased'));
|
||||
expect(user.items.pets[animal]).to.be.empty;
|
||||
expect(user.items.mounts[animal]).to.equal(null);
|
||||
});
|
||||
|
||||
it('does not increment beastMasterCount if any pet is level 0 (released)', () => {
|
||||
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
|
||||
user.items.pets[animal] = 0;
|
||||
|
||||
releaseBoth(user);
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('does not increment beastMasterCount if any pet is missing (null)', () => {
|
||||
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
|
||||
user.items.pets[animal] = null;
|
||||
releaseBoth(user);
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('does not increment beastMasterCount if any pet is missing (undefined)', () => {
|
||||
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
|
||||
delete user.items.pets[animal];
|
||||
releaseBoth(user);
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('releases mounts', () => {
|
||||
let [, message] = releaseBoth(user);
|
||||
let message = releaseBoth(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('mountsAndPetsReleased'));
|
||||
expect(user.items.mounts[animal]).to.equal(null);
|
||||
});
|
||||
|
||||
it('does not increase mountMasterCount achievement if mount is missing (null)', () => {
|
||||
let mountMasterCountBeforeRelease = user.achievements.mountMasterCount;
|
||||
user.items.mounts[animal] = null;
|
||||
|
||||
releaseBoth(user);
|
||||
|
||||
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('does not increase mountMasterCount achievement if mount is missing (undefined)', () => {
|
||||
let mountMasterCountBeforeRelease = user.achievements.mountMasterCount;
|
||||
delete user.items.mounts[animal];
|
||||
|
||||
releaseBoth(user);
|
||||
|
||||
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('removes drop currentPet', () => {
|
||||
let petInfo = content.petInfo[user.items.currentPet];
|
||||
expect(petInfo.type).to.equal('drop');
|
||||
|
||||
@@ -14,8 +14,12 @@ describe('shared.ops.releaseMounts', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
for (let k in content.pets) {
|
||||
user.items.mounts[k] = content.pets[k];
|
||||
user.items.mounts[k] = true;
|
||||
}
|
||||
|
||||
user.items.currentMount = animal;
|
||||
user.items.mounts[animal] = true;
|
||||
user.balance = 1;
|
||||
});
|
||||
|
||||
@@ -32,7 +36,7 @@ describe('shared.ops.releaseMounts', () => {
|
||||
});
|
||||
|
||||
it('releases mounts', () => {
|
||||
let [, message] = releaseMounts(user);
|
||||
let message = releaseMounts(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('mountsReleased'));
|
||||
expect(user.items.mounts[animal]).to.equal(null);
|
||||
@@ -60,10 +64,27 @@ describe('shared.ops.releaseMounts', () => {
|
||||
|
||||
it('increases mountMasterCount achievement', () => {
|
||||
releaseMounts(user);
|
||||
|
||||
expect(user.achievements.mountMasterCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('does not increase mountMasterCount achievement if mount is missing (null)', () => {
|
||||
let mountMasterCountBeforeRelease = user.achievements.mountMasterCount;
|
||||
user.items.mounts[animal] = null;
|
||||
|
||||
releaseMounts(user);
|
||||
|
||||
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('does not increase mountMasterCount achievement if mount is missing (undefined)', () => {
|
||||
let mountMasterCountBeforeRelease = user.achievements.mountMasterCount;
|
||||
delete user.items.mounts[animal];
|
||||
|
||||
releaseMounts(user);
|
||||
|
||||
expect(user.achievements.mountMasterCount).to.equal(mountMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('subtracts gems from balance', () => {
|
||||
releaseMounts(user);
|
||||
|
||||
|
||||
@@ -14,8 +14,12 @@ describe('shared.ops.releasePets', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
for (let k in content.pets) {
|
||||
user.items.pets[k] = content.pets[k];
|
||||
user.items.pets[k] = 5;
|
||||
}
|
||||
|
||||
user.items.currentPet = animal;
|
||||
user.items.pets[animal] = 5;
|
||||
user.balance = 1;
|
||||
});
|
||||
|
||||
@@ -32,7 +36,7 @@ describe('shared.ops.releasePets', () => {
|
||||
});
|
||||
|
||||
it('releases pets', () => {
|
||||
let [, message] = releasePets(user);
|
||||
let message = releasePets(user)[1];
|
||||
|
||||
expect(message).to.equal(i18n.t('petsReleased'));
|
||||
expect(user.items.pets[animal]).to.equal(0);
|
||||
@@ -69,4 +73,29 @@ describe('shared.ops.releasePets', () => {
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('does not increment beastMasterCount if any pet is level 0 (released)', () => {
|
||||
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
|
||||
|
||||
user.items.pets[animal] = 0;
|
||||
releasePets(user);
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('does not increment beastMasterCount if any pet is missing (null)', () => {
|
||||
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
|
||||
user.items.pets[animal] = null;
|
||||
releasePets(user);
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
|
||||
});
|
||||
|
||||
it('does not increment beastMasterCount if any pet is missing (undefined)', () => {
|
||||
let beastMasterCountBeforeRelease = user.achievements.beastMasterCount;
|
||||
delete user.items.pets[animal];
|
||||
releasePets(user);
|
||||
|
||||
expect(user.achievements.beastMasterCount).to.equal(beastMasterCountBeforeRelease);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,6 +65,16 @@ describe('shared.ops.sell', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an error when the requested amount is above the available amount', (done) => {
|
||||
try {
|
||||
sell(user, {params: { type, key }, query: {amount: 2} });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotFound);
|
||||
expect(err.message).to.equal(i18n.t('userItemsNotEnough', {type}));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('reduces item count from user', () => {
|
||||
sell(user, {params: { type, key } });
|
||||
|
||||
|
||||
@@ -38,6 +38,18 @@ module.exports = {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/stripe': {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/amazon': {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/paypal': {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
// CSS Sourcemaps off by default because relative paths are "buggy"
|
||||
// with this option, according to the CSS-Loader README
|
||||
|
||||
@@ -1,552 +1,546 @@
|
||||
.promo_android {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1712px -356px;
|
||||
background-position: -1748px 0px;
|
||||
width: 175px;
|
||||
height: 175px;
|
||||
}
|
||||
.promo_aquatic_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -982px -148px;
|
||||
background-position: -589px -148px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201602 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -704px -1042px;
|
||||
background-position: -1606px -295px;
|
||||
width: 141px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201603 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -846px -1042px;
|
||||
background-position: -1606px -590px;
|
||||
width: 141px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201604 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1432px -442px;
|
||||
background-position: -1325px -441px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201605 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -282px -1042px;
|
||||
background-position: -987px -894px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201606 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -699px -148px;
|
||||
background-position: -306px -148px;
|
||||
width: 140px;
|
||||
height: 447px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201607 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -559px 0px;
|
||||
background-position: 0px -305px;
|
||||
width: 139px;
|
||||
height: 588px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201608 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -423px -1042px;
|
||||
background-position: -1128px -894px;
|
||||
width: 140px;
|
||||
height: 439px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201609 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -564px -1042px;
|
||||
background-position: -1269px -894px;
|
||||
width: 139px;
|
||||
height: 438px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201610 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1291px -442px;
|
||||
background-position: -282px -894px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201611 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1291px 0px;
|
||||
background-position: -141px -894px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201612 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1150px -442px;
|
||||
background-position: 0px -894px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201701 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1150px 0px;
|
||||
background-position: -705px -894px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201702 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -852px -600px;
|
||||
background-position: -1041px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201703 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -710px -600px;
|
||||
background-position: -1041px -442px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201704 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -840px -148px;
|
||||
background-position: -1183px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201705 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -568px -600px;
|
||||
background-position: -447px -148px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201706 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -426px -600px;
|
||||
background-position: -1183px -442px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_backgrounds_armoire_201707 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -899px -442px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_bees {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -284px -600px;
|
||||
background-position: -899px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_bundle_feathered {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -142px -600px;
|
||||
background-position: -757px -442px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_bundle_splashy {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -757px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_burnout {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -306px -295px;
|
||||
background-position: -306px -596px;
|
||||
width: 219px;
|
||||
height: 240px;
|
||||
}
|
||||
.promo_chairs_glasses {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1818px -532px;
|
||||
background-position: -1854px -352px;
|
||||
width: 51px;
|
||||
height: 210px;
|
||||
}
|
||||
.promo_checkin_incentives {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -988px -1042px;
|
||||
background-position: -1606px 0px;
|
||||
width: 141px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_classes_fall_2014 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -704px -1337px;
|
||||
background-position: 0px -1484px;
|
||||
width: 321px;
|
||||
height: 100px;
|
||||
}
|
||||
.promo_classes_fall_2015 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1130px -1190px;
|
||||
background-position: -823px -1336px;
|
||||
width: 377px;
|
||||
height: 99px;
|
||||
}
|
||||
.promo_classes_fall_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -442px;
|
||||
background-position: -1606px -885px;
|
||||
width: 103px;
|
||||
height: 348px;
|
||||
}
|
||||
.promo_coffee_mug {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1712px 0px;
|
||||
background-position: -526px -596px;
|
||||
width: 200px;
|
||||
height: 179px;
|
||||
}
|
||||
.promo_contrib_spotlight_Keith {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1802px -1477px;
|
||||
background-position: -1467px -442px;
|
||||
width: 87px;
|
||||
height: 111px;
|
||||
}
|
||||
.promo_contrib_spotlight_alys {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1803px -1365px;
|
||||
background-position: -1748px -1318px;
|
||||
width: 90px;
|
||||
height: 110px;
|
||||
}
|
||||
.promo_contrib_spotlight_beffymaroo {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1712px -806px;
|
||||
background-position: -1748px -626px;
|
||||
width: 114px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_contrib_spotlight_blade {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1712px -1477px;
|
||||
background-position: -1748px -1429px;
|
||||
width: 89px;
|
||||
height: 111px;
|
||||
}
|
||||
.promo_contrib_spotlight_cantras {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -900px;
|
||||
background-position: -1467px -663px;
|
||||
width: 87px;
|
||||
height: 109px;
|
||||
}
|
||||
.promo_contrib_spotlight_dewines {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -791px;
|
||||
background-position: -1467px -554px;
|
||||
width: 89px;
|
||||
height: 108px;
|
||||
}
|
||||
.promo_contrib_spotlight_megan {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1813px -1045px;
|
||||
background-position: -1748px -1206px;
|
||||
width: 90px;
|
||||
height: 111px;
|
||||
}
|
||||
.promo_contrib_spotlight_shanaqui {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1712px -1365px;
|
||||
background-position: -1748px -1094px;
|
||||
width: 90px;
|
||||
height: 111px;
|
||||
}
|
||||
.promo_cow {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -141px -1042px;
|
||||
background-position: -564px -894px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_cupid_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px 0px;
|
||||
background-position: -1467px 0px;
|
||||
width: 138px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_dilatoryDistress {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -182px -1632px;
|
||||
background-position: -1634px -1484px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_egg_mounts {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px -1484px;
|
||||
background-position: 0px -1336px;
|
||||
width: 280px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_enchanted_armoire {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1026px -1337px;
|
||||
background-position: -1201px -1336px;
|
||||
width: 374px;
|
||||
height: 76px;
|
||||
}
|
||||
.promo_enchanted_armoire_201507 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1088px -1484px;
|
||||
background-position: -322px -1484px;
|
||||
width: 217px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_enchanted_armoire_201508 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1712px -954px;
|
||||
background-position: -540px -1484px;
|
||||
width: 180px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_enchanted_armoire_201509 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -637px -1632px;
|
||||
background-position: -1179px -1484px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_enchanted_armoire_201511 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1712px -1183px;
|
||||
background-position: -1748px -912px;
|
||||
width: 122px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_enchanted_armoire_201601 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px -1632px;
|
||||
background-position: -1088px -1484px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_fairy_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px -600px;
|
||||
background-position: -140px -305px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_floral_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1712px -532px;
|
||||
background-position: -1748px -352px;
|
||||
width: 105px;
|
||||
height: 273px;
|
||||
}
|
||||
.promo_ghost_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1432px 0px;
|
||||
background-position: -846px -894px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_good_luck {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1467px -1258px;
|
||||
width: 110px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_habitica {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1712px -180px;
|
||||
background-position: -1748px -176px;
|
||||
width: 175px;
|
||||
height: 175px;
|
||||
}
|
||||
.promo_habitica_sticker {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px -295px;
|
||||
background-position: 0px 0px;
|
||||
width: 305px;
|
||||
height: 304px;
|
||||
}
|
||||
.promo_habitoween_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px -1042px;
|
||||
background-position: -423px -894px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_haunted_hair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1712px -1045px;
|
||||
background-position: -1748px -774px;
|
||||
width: 100px;
|
||||
height: 137px;
|
||||
}
|
||||
.promo_holly_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -994px -600px;
|
||||
background-position: -1325px 0px;
|
||||
width: 141px;
|
||||
height: 440px;
|
||||
}
|
||||
.promo_item_notif {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -838px -1484px;
|
||||
background-position: 0px -1585px;
|
||||
width: 249px;
|
||||
height: 102px;
|
||||
}
|
||||
.promo_jackalope {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -561px -1484px;
|
||||
background-position: -281px -1336px;
|
||||
width: 276px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_king_manta {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -558px -1336px;
|
||||
width: 264px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_more_checkin_incentives {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -699px 0px;
|
||||
background-position: -306px 0px;
|
||||
width: 450px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201405 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -364px -1632px;
|
||||
background-position: -614px -1585px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201406 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1432px -884px;
|
||||
background-position: -1606px -1234px;
|
||||
width: 90px;
|
||||
height: 96px;
|
||||
}
|
||||
.promo_mystery_201407 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1870px -669px;
|
||||
background-position: -1854px -563px;
|
||||
width: 42px;
|
||||
height: 62px;
|
||||
}
|
||||
.promo_mystery_201408 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1507px -1042px;
|
||||
background-position: -1839px -1206px;
|
||||
width: 60px;
|
||||
height: 71px;
|
||||
}
|
||||
.promo_mystery_201409 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1494px -1484px;
|
||||
background-position: -432px -1585px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201410 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1833px -1274px;
|
||||
background-position: -1839px -1094px;
|
||||
width: 72px;
|
||||
height: 63px;
|
||||
}
|
||||
.promo_mystery_201411 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -91px -1632px;
|
||||
background-position: -906px -1484px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201412 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1870px -602px;
|
||||
background-position: -1869px -1003px;
|
||||
width: 42px;
|
||||
height: 66px;
|
||||
}
|
||||
.promo_mystery_201501 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1663px -791px;
|
||||
background-position: -1863px -708px;
|
||||
width: 48px;
|
||||
height: 63px;
|
||||
}
|
||||
.promo_mystery_201502 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -910px -1632px;
|
||||
background-position: -815px -1484px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201503 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1092px -1632px;
|
||||
background-position: -1270px -1484px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201504 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1508px -1190px;
|
||||
background-position: -1839px -1318px;
|
||||
width: 60px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_mystery_201505 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1585px -1484px;
|
||||
background-position: -1452px -1484px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201506 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1870px -532px;
|
||||
background-position: -1871px -912px;
|
||||
width: 42px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_mystery_201507 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -1222px;
|
||||
background-position: -1467px -773px;
|
||||
width: 90px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_mystery_201508 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1150px -884px;
|
||||
background-position: -140px -747px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201509 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -273px -1632px;
|
||||
background-position: -341px -1585px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201510 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1413px -1042px;
|
||||
background-position: -721px -1484px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201511 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -455px -1632px;
|
||||
background-position: -523px -1585px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201512 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1835px -1183px;
|
||||
background-position: -1863px -626px;
|
||||
width: 60px;
|
||||
height: 81px;
|
||||
}
|
||||
.promo_mystery_201601 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1712px -1274px;
|
||||
background-position: -1748px -1003px;
|
||||
width: 120px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201602 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -728px -1632px;
|
||||
background-position: -1543px -1484px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201603 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -819px -1632px;
|
||||
background-position: -1361px -1484px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201604 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1306px -1484px;
|
||||
background-position: -1467px -1076px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201605 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1001px -1632px;
|
||||
background-position: -997px -1484px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201606 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -1010px;
|
||||
background-position: -1467px -879px;
|
||||
width: 90px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_mystery_201607 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -546px -1632px;
|
||||
background-position: -250px -1585px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201608 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1400px -1484px;
|
||||
background-position: -1467px -985px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201609 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1291px -884px;
|
||||
background-position: -1467px -1167px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201610 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1827px -806px;
|
||||
background-position: -1849px -774px;
|
||||
width: 63px;
|
||||
height: 84px;
|
||||
}
|
||||
.promo_mystery_201611 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -1328px;
|
||||
width: 90px;
|
||||
height: 99px;
|
||||
}
|
||||
.promo_mystery_201612 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px 0px;
|
||||
width: 558px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_mystery_201701 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1573px -1116px;
|
||||
width: 90px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_mystery_201702 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -281px -1484px;
|
||||
width: 279px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201703 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: -1130px -1042px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 932 KiB After Width: | Height: | Size: 929 KiB |
@@ -1,444 +1,462 @@
|
||||
.promo_mystery_201611 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1811px -1322px;
|
||||
width: 90px;
|
||||
height: 99px;
|
||||
}
|
||||
.promo_mystery_201612 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px 0px;
|
||||
width: 558px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_mystery_201701 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1811px -1125px;
|
||||
width: 90px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_mystery_201702 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1510px -890px;
|
||||
width: 279px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201703 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1510px -151px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201704 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -833px -343px;
|
||||
background-position: -1811px -1422px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201705 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1667px -151px;
|
||||
background-position: -1510px -299px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201706 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1547px -1177px;
|
||||
background-position: -1811px -663px;
|
||||
width: 111px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_201707 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1811px -1231px;
|
||||
width: 105px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_mystery_3014 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -904px -1517px;
|
||||
background-position: -620px -1491px;
|
||||
width: 217px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_naming_day_2017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1651px -595px;
|
||||
width: 159px;
|
||||
height: 141px;
|
||||
}
|
||||
.promo_new_hair_fall2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -425px -927px;
|
||||
background-position: -1368px -442px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_orca {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1547px -1026px;
|
||||
background-position: -1811px -557px;
|
||||
width: 105px;
|
||||
height: 105px;
|
||||
}
|
||||
.promo_orcas {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -849px -295px;
|
||||
width: 219px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_partyhats {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1547px -1268px;
|
||||
background-position: -1811px -1513px;
|
||||
width: 115px;
|
||||
height: 47px;
|
||||
}
|
||||
.promo_pastel_skin {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -983px -1369px;
|
||||
background-position: -659px -702px;
|
||||
width: 330px;
|
||||
height: 83px;
|
||||
}
|
||||
.customize-option.promo_pastel_skin {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1008px -1384px;
|
||||
background-position: -684px -717px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_pastel_skin_hair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -848px -1078px;
|
||||
background-position: -824px -1049px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
}
|
||||
.customize-option.promo_pastel_skin_hair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -873px -1093px;
|
||||
background-position: -849px -1064px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_peppermint_flame {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1254px -589px;
|
||||
background-position: -1368px -884px;
|
||||
width: 140px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_pet_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1249px -927px;
|
||||
background-position: -1226px -884px;
|
||||
width: 140px;
|
||||
height: 147px;
|
||||
}
|
||||
.customize-option.promo_pet_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1274px -942px;
|
||||
background-position: -1251px -899px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_pyromancer {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1547px -875px;
|
||||
background-position: -1811px -443px;
|
||||
width: 113px;
|
||||
height: 113px;
|
||||
}
|
||||
.promo_rainbow_armor {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1875px -1246px;
|
||||
background-position: -1811px -1021px;
|
||||
width: 92px;
|
||||
height: 103px;
|
||||
}
|
||||
.promo_redesign_header {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -1491px;
|
||||
width: 166px;
|
||||
height: 188px;
|
||||
}
|
||||
.promo_redesign_login {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -559px 0px;
|
||||
width: 524px;
|
||||
height: 274px;
|
||||
}
|
||||
.promo_redesign_tavern {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -328px -515px;
|
||||
width: 375px;
|
||||
height: 186px;
|
||||
}
|
||||
.promo_seafoam {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1226px -442px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_seasonal_shop_fall_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1667px -594px;
|
||||
background-position: -1510px -742px;
|
||||
width: 279px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_shimmer_hair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1314px -1369px;
|
||||
background-position: -328px -702px;
|
||||
width: 330px;
|
||||
height: 83px;
|
||||
}
|
||||
.promo_shimmer_potions {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -927px;
|
||||
background-position: -1226px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_shinySeeds {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -142px -927px;
|
||||
background-position: -1368px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_splashy_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -704px -515px;
|
||||
width: 375px;
|
||||
height: 186px;
|
||||
}
|
||||
.customize-option.promo_splashy_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -729px -530px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_splashyskins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -1628px;
|
||||
background-position: -1300px -1200px;
|
||||
width: 198px;
|
||||
height: 91px;
|
||||
}
|
||||
.customize-option.promo_splashyskins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -25px -1643px;
|
||||
background-position: -1325px -1215px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_spooky_sparkles_fall_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1667px -299px;
|
||||
background-position: -1510px -447px;
|
||||
width: 140px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_spring_classes_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -620px -1369px;
|
||||
background-position: -687px -937px;
|
||||
width: 362px;
|
||||
height: 102px;
|
||||
}
|
||||
.promo_spring_classes_2017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -1369px;
|
||||
background-position: -1179px -1049px;
|
||||
width: 309px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_springclasses2014 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -326px -1517px;
|
||||
background-position: -784px -1396px;
|
||||
width: 288px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_springclasses2015 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -615px -1517px;
|
||||
background-position: -1073px -1396px;
|
||||
width: 288px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_staff_spotlight_Lemoness {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1808px -447px;
|
||||
background-position: -1811px -148px;
|
||||
width: 102px;
|
||||
height: 146px;
|
||||
}
|
||||
.promo_staff_spotlight_Viirus {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1547px -573px;
|
||||
background-position: -1811px 0px;
|
||||
width: 119px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_staff_spotlight_paglias {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1547px -724px;
|
||||
background-position: -1811px -295px;
|
||||
width: 99px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_steampunk_3017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -566px -927px;
|
||||
background-position: -282px -1049px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_summer_classes_2014 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -848px -1226px;
|
||||
background-position: -257px -937px;
|
||||
width: 429px;
|
||||
height: 102px;
|
||||
}
|
||||
.promo_summer_classes_2015 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1667px -1427px;
|
||||
background-position: -1510px -1391px;
|
||||
width: 300px;
|
||||
height: 88px;
|
||||
}
|
||||
.promo_summer_classes_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -848px -927px;
|
||||
background-position: -423px -1049px;
|
||||
width: 400px;
|
||||
height: 150px;
|
||||
}
|
||||
.promo_summer_classes_2017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1254px 0px;
|
||||
background-position: -1084px 0px;
|
||||
width: 141px;
|
||||
height: 588px;
|
||||
}
|
||||
.promo_takeThis_gear {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1278px -1226px;
|
||||
background-position: -1811px -842px;
|
||||
width: 114px;
|
||||
height: 87px;
|
||||
}
|
||||
.promo_takethis_armor {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -199px -1628px;
|
||||
background-position: -1811px -754px;
|
||||
width: 114px;
|
||||
height: 87px;
|
||||
}
|
||||
.promo_task_planning {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1396px -181px;
|
||||
background-position: -423px -1200px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
.promo_turkey_day_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -284px -927px;
|
||||
background-position: -141px -1049px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_unconventional_armor {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1890px -1149px;
|
||||
background-position: -1733px -1038px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_unconventional_armor2 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1890px -1074px;
|
||||
background-position: -1811px -1561px;
|
||||
width: 70px;
|
||||
height: 74px;
|
||||
}
|
||||
.promo_updos {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1808px -299px;
|
||||
background-position: -1651px -447px;
|
||||
width: 156px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_veteran_pets {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1275px -1517px;
|
||||
background-position: -1362px -1396px;
|
||||
width: 146px;
|
||||
height: 75px;
|
||||
}
|
||||
.promo_winter_classes_2016 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -932px -782px;
|
||||
background-position: -423px -1396px;
|
||||
width: 360px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_winter_classes_2017 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -782px;
|
||||
background-position: -257px -792px;
|
||||
width: 432px;
|
||||
height: 144px;
|
||||
}
|
||||
.promo_winter_fireworks {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1203px -1078px;
|
||||
background-position: -1084px -589px;
|
||||
width: 138px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_winterclasses2015 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -1517px;
|
||||
background-position: -974px -1200px;
|
||||
width: 325px;
|
||||
height: 110px;
|
||||
}
|
||||
.promo_wintery_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -707px -927px;
|
||||
background-position: 0px -1049px;
|
||||
width: 140px;
|
||||
height: 441px;
|
||||
}
|
||||
.customize-option.promo_wintery_skins {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -732px -942px;
|
||||
background-position: -25px -1064px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_winteryhair {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1122px -1517px;
|
||||
background-position: -1050px -937px;
|
||||
width: 152px;
|
||||
height: 75px;
|
||||
}
|
||||
.avatar_variety {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -433px -782px;
|
||||
background-position: -690px -792px;
|
||||
width: 498px;
|
||||
height: 95px;
|
||||
}
|
||||
.npc_viirus {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -1720px;
|
||||
background-position: -1811px -930px;
|
||||
width: 108px;
|
||||
height: 90px;
|
||||
}
|
||||
.party_preview {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -562px;
|
||||
background-position: 0px -295px;
|
||||
width: 451px;
|
||||
height: 219px;
|
||||
}
|
||||
.promo_backtoschool {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1396px -875px;
|
||||
background-position: -469px -1491px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.promo_cooking {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -452px -562px;
|
||||
background-position: -452px -295px;
|
||||
width: 396px;
|
||||
height: 219px;
|
||||
}
|
||||
.promo_startingover {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1396px -573px;
|
||||
background-position: -318px -1491px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.promo_valentines {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -310px -1369px;
|
||||
background-position: -664px -1200px;
|
||||
width: 309px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_working_out {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1667px 0px;
|
||||
background-position: -1510px 0px;
|
||||
width: 300px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_arts_crafts {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -926px -277px;
|
||||
background-position: 0px -792px;
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
}
|
||||
.scene_buying_rewards {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1667px -1246px;
|
||||
background-position: -1510px -1210px;
|
||||
width: 207px;
|
||||
height: 180px;
|
||||
}
|
||||
.scene_coding {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1396px -1026px;
|
||||
background-position: -167px -1491px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_dailies {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -926px 0px;
|
||||
background-position: 0px -515px;
|
||||
width: 327px;
|
||||
height: 276px;
|
||||
}
|
||||
.scene_eco_friendly {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1667px -1074px;
|
||||
background-position: -1510px -1038px;
|
||||
width: 222px;
|
||||
height: 171px;
|
||||
}
|
||||
.scene_guilds {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px -312px;
|
||||
width: 498px;
|
||||
height: 249px;
|
||||
}
|
||||
.scene_habitica_house {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: 0px 0px;
|
||||
width: 585px;
|
||||
height: 311px;
|
||||
}
|
||||
.scene_habits {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -926px -534px;
|
||||
width: 306px;
|
||||
height: 174px;
|
||||
}
|
||||
.scene_meditation {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1396px -724px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_phone_peek {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1396px -1177px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_raking_leaves {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -586px -343px;
|
||||
width: 246px;
|
||||
height: 198px;
|
||||
}
|
||||
.scene_todos {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1396px -377px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
.scene_video_games {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -586px 0px;
|
||||
width: 339px;
|
||||
height: 342px;
|
||||
}
|
||||
.welcome_basic_avatars {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1667px -908px;
|
||||
width: 246px;
|
||||
height: 165px;
|
||||
}
|
||||
.welcome_promo_party {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1396px 0px;
|
||||
width: 270px;
|
||||
height: 180px;
|
||||
}
|
||||
.welcome_sample_tasks {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-1.png);
|
||||
background-position: -1667px -742px;
|
||||
width: 246px;
|
||||
height: 165px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 961 KiB After Width: | Height: | Size: 559 KiB |
@@ -0,0 +1,78 @@
|
||||
.scene_guilds {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: 0px -312px;
|
||||
width: 498px;
|
||||
height: 249px;
|
||||
}
|
||||
.scene_habitica_house {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: 0px 0px;
|
||||
width: 585px;
|
||||
height: 311px;
|
||||
}
|
||||
.scene_habits {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: 0px -562px;
|
||||
width: 306px;
|
||||
height: 174px;
|
||||
}
|
||||
.scene_meditation {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -422px -737px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_pet_hatching {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -586px -343px;
|
||||
width: 315px;
|
||||
height: 195px;
|
||||
}
|
||||
.scene_phone_peek {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -271px -737px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.scene_raking_leaves {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -926px 0px;
|
||||
width: 246px;
|
||||
height: 198px;
|
||||
}
|
||||
.scene_task_list {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -926px -199px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
.scene_todos {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -926px -395px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
.scene_video_games {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -586px 0px;
|
||||
width: 339px;
|
||||
height: 342px;
|
||||
}
|
||||
.welcome_basic_avatars {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -307px -562px;
|
||||
width: 246px;
|
||||
height: 165px;
|
||||
}
|
||||
.welcome_promo_party {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: 0px -737px;
|
||||
width: 270px;
|
||||
height: 180px;
|
||||
}
|
||||
.welcome_sample_tasks {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-2.png);
|
||||
background-position: -554px -562px;
|
||||
width: 246px;
|
||||
height: 165px;
|
||||
}
|
||||
|
After Width: | Height: | Size: 440 KiB |
|
Before Width: | Height: | Size: 506 KiB After Width: | Height: | Size: 517 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 162 KiB |
@@ -1,414 +1,546 @@
|
||||
.Pet-Turtle-Red {
|
||||
.Pet-TRex-Red {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -82px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -492px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -164px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-White {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Zombie {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -82px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Base {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -164px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-CottonCandyBlue {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -246px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-CottonCandyPink {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -246px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Desert {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Golden {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -82px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Red {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -164px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -246px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -328px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-White {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -328px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Zombie {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -328px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Base {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-CottonCandyBlue {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -82px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-CottonCandyPink {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -164px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Desert {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -246px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Golden {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -328px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Red {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -410px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -410px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -410px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-White {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -410px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Zombie {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -492px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Aquatic {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -492px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Base {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -492px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-CottonCandyBlue {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -492px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-CottonCandyPink {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Cupid {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -82px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Desert {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -164px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Fairy {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -246px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Floral {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -328px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Ghost {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -410px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Golden {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Holly {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -574px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Peppermint {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -574px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Red {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -574px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-RoyalPurple {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -574px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -574px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shimmer {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -82px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Spooky {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -164px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Thunderstorm {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -246px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Veteran {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -328px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-White {
|
||||
.Pet-TRex-Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -410px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Zombie {
|
||||
.Pet-TRex-Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -164px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-White {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-Zombie {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -82px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Base {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -164px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-CottonCandyBlue {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -246px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-CottonCandyPink {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -246px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Desert {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Golden {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -82px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Red {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -164px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -246px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -328px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-White {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -328px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Zombie {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -328px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turkey-Base {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turkey-Gilded {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -82px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Base {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -164px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-CottonCandyBlue {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -246px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-CottonCandyPink {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -328px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Desert {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -410px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Golden {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -410px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Red {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -410px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -410px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -492px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-White {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -492px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Zombie {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -492px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Base {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -492px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-CottonCandyBlue {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-CottonCandyPink {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -82px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Desert {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -164px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Golden {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -246px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Red {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -328px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -410px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -492px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-White {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -574px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Zombie {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -574px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Base {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -574px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-CottonCandyBlue {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -574px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-CottonCandyPink {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -574px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Desert {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Golden {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -82px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Red {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -164px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -246px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -328px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-White {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Zombie {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -492px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet_HatchingPotion_Aquatic {
|
||||
.Pet-Wolf-Aquatic {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -574px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Base {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-CottonCandyBlue {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-CottonCandyPink {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Cupid {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Desert {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Fairy {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Floral {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Ghost {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -82px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Golden {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -164px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Holly {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -246px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Peppermint {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -328px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Red {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -410px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-RoyalPurple {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -492px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -574px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shimmer {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -738px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Spooky {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -738px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Thunderstorm {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -738px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Veteran {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -738px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-White {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -738px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Zombie {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -738px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet_HatchingPotion_Aquatic {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: 0px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Base {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -490px -600px;
|
||||
background-position: -490px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_CottonCandyBlue {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -49px -600px;
|
||||
background-position: -49px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_CottonCandyPink {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -98px -600px;
|
||||
background-position: -98px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Cupid {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -147px -600px;
|
||||
background-position: -147px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Desert {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -196px -600px;
|
||||
background-position: -196px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Fairy {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -245px -600px;
|
||||
background-position: -245px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Floral {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -294px -600px;
|
||||
background-position: -294px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Ghost {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -343px -600px;
|
||||
background-position: -343px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Golden {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -392px -600px;
|
||||
background-position: -392px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Holly {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -441px -600px;
|
||||
background-position: -441px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Peppermint {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -574px -500px;
|
||||
background-position: -738px -600px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Purple {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -539px -600px;
|
||||
background-position: -539px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Red {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -588px -600px;
|
||||
background-position: -588px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_RoyalPurple {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px 0px;
|
||||
background-position: -637px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px -52px;
|
||||
background-position: -686px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Shimmer {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px -104px;
|
||||
background-position: -735px -700px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px -156px;
|
||||
background-position: 0px -752px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Spooky {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px -208px;
|
||||
background-position: -49px -752px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Thunderstorm {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px -260px;
|
||||
background-position: -98px -752px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_White {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px -312px;
|
||||
background-position: -147px -752px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
.Pet_HatchingPotion_Zombie {
|
||||
background-image: url(/static/sprites/spritesmith-main-18.png);
|
||||
background-position: -656px -364px;
|
||||
background-position: -196px -752px;
|
||||
width: 48px;
|
||||
height: 51px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 154 KiB |