Compare commits

..

6 Commits

Author SHA1 Message Date
Sabe Jones 93437a00dd 3.78.0 2017-03-02 21:05:49 +00:00
Sabe Jones 31afa0345e chore(news): Bailey 2017-03-02 2017-03-02 20:59:03 +00:00
Sabe Jones b2c278b00a fix(sprites): correct shop canvas 2017-03-02 20:58:42 +00:00
Sabe Jones b54037f230 chore(event): disable Cupid Potions 2017-03-02 20:49:02 +00:00
Sabe Jones 488dcb916a chore(sprites): compile 2017-03-02 20:44:25 +00:00
Sabe Jones 10792b65be feat(content): add Armoire and BGs 2017-03 2017-03-02 20:44:00 +00:00
3957 changed files with 93661 additions and 192835 deletions
-1
View File
@@ -1,7 +1,6 @@
{
"presets": ["es2015"],
"plugins": [
"transform-object-rest-spread",
["transform-async-to-module-method", {
"module": "bluebird",
"method": "coroutine"
+1 -1
View File
@@ -14,7 +14,7 @@ files:
owner: root
group: users
content: |
$(ls -td /opt/elasticbeanstalk/node-install/node-* | head -1)/bin/npm install -g npm@5
$(ls -td /opt/elasticbeanstalk/node-install/node-* | head -1)/bin/npm install -g npm@4
container_commands:
01_makeBabel:
command: "touch /tmp/.babel.json"
+5 -2
View File
@@ -8,13 +8,16 @@ dist/
dist-client/
# Not linted
migrations/*
website/client-old/
scripts/*
test/server_side/**/*
test/client-old/spec/**/*
# Temporarilly disabled. These should be removed when the linting errors are fixed TODO
migrations/*
scripts/*
website/common/script/content/index.js
website/common/browserify.js
test/content/**/*
Gruntfile.js
gulpfile.js
gulp
+1 -1
View File
@@ -1,6 +1,6 @@
# Reporting Bugs
[Please see these instructions for reporting bugs](https://github.com/HabitRPG/habitica/issues/2760)
[Please see these instructions for reporting bugs](https://github.com/HabitRPG/habitrpg/issues/2760)
# Pull Request
+8 -6
View File
@@ -1,19 +1,21 @@
[//]: # (Before logging this issue, please post to the Report a Bug guild from the Habitica website's Help menu. Most bugs can be handled quickly there. If a GitHub issue is needed, you will be advised of that by a moderator or staff member -- a player with a dark blue or purple name. It is recommended that you don't create a new issue unless advised to.)
[//]: # (Before logging this issue, look through common problems at https://github.com/HabitRPG/habitrpg/issues If you find your issue there, read at least the first post to see if there is a workaround for you)
[//]: # (Bugs in the mobile apps can also be reported there.)
[//]: # (Github is primarily used for reporting bugs. If you have a feature request, use "Help > Request a Feature" so that the feature request can be vetted by the larger Habitica community)
[//]: # (If you have a feature request, use "Help > Request a Feature", not GitHub or the Report a Bug guild.)
[//]: # (To report a bug in one of the mobile apps, please report it in the correct repository. Android: https://github.com/HabitRPG/habitrpg-android, iOS: https://github.com/HabitRPG/habitrpg-ios)
[//]: # (For more guidelines see https://github.com/HabitRPG/habitica/issues/2760)
[//]: # (For more guidelines see https://github.com/HabitRPG/habitrpg/issues/2760)
[//]: # (Fill out relevant information - UUID is found in Settings -> API)
### General Info
General Info
* UUID:
* Browser:
* OS:
### Description
[//]: # (Describe bug in detail here. Include screenshots if helpful.)
[//]: # (Describe bug in detail here. Include pictures if helpful.)
#### Console Errors
[//]: # (Include any JavaScript console errors here.)
+10 -15
View File
@@ -2,9 +2,6 @@ language: node_js
node_js:
- '6'
sudo: required
dist: precise
services:
- mongodb
addons:
apt:
sources:
@@ -13,26 +10,24 @@ addons:
- g++-4.8
before_install:
- $CXX --version
- npm install -g npm@5
- npm install -g npm@4
- if [ $REQUIRES_SERVER ]; then sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10; echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list; sudo apt-get update; sudo apt-get install mongodb-org-server; fi
install:
- npm install &> npm.install.log || (cat npm.install.log; false)
before_script:
- npm run test:build
- cp config.json.example config.json
- sleep 15
script:
- npm run $TEST
- if [ $COVERAGE ]; then ./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js; fi
- if [ $REQUIRES_SERVER ]; then until nc -z localhost 27017; do echo Waiting for MongoDB; sleep 1; done; export DISPLAY=:99; fi
after_script:
- ./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js
script: npm run $TEST
env:
global:
- CXX=g++-4.8
- DISABLE_REQUEST_LOGGING=true
matrix:
- TEST="lint"
- TEST="test:api-v3" REQUIRES_SERVER=true COVERAGE=true
- TEST="test:api-v3" REQUIRES_SERVER=true
- TEST="test:sanity"
- TEST="test:content" COVERAGE=true
- TEST="test:common" COVERAGE=true
- TEST="client:unit" COVERAGE=true
- TEST="apidoc"
- TEST="test:content"
- TEST="test:common"
- TEST="test:karma"
- TEST="client:unit"
+45 -22
View File
@@ -1,22 +1,45 @@
FROM node:boron
# Upgrade NPM to v5 (Yarn is needed because of this bug https://github.com/npm/npm/issues/16807)
# The used solution is suggested here https://github.com/npm/npm/issues/16807#issuecomment-313591975
RUN yarn global add npm@5
# 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 cp config.json.example config.json
RUN npm install
RUN bower install --allow-root
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["npm", "start"]
FROM ubuntu:trusty
MAINTAINER Sabe Jones <sabe@habitica.com>
# Avoid ERROR: invoke-rc.d: policy-rc.d denied execution of start.
RUN echo -e '#!/bin/sh\nexit 0' > /usr/sbin/policy-rc.d
# Install prerequisites
RUN apt-get update
RUN apt-get install -y \
build-essential \
curl \
git \
libfontconfig1 \
libfreetype6 \
libkrb5-dev \
python
# Install NodeJS
RUN curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
RUN apt-get install -y nodejs
# Install npm@latest
RUN curl -sL https://www.npmjs.org/install.sh | sh
# Clean up package management
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*
# Install global packages
RUN npm install -g gulp grunt-cli bower mocha
# Clone Habitica repo and install dependencies
WORKDIR /habitrpg
RUN git clone https://github.com/HabitRPG/habitica.git /habitrpg
RUN npm install
RUN bower install --allow-root
# Create environment config file and build directory
RUN cp config.json.example config.json
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["npm", "start"]
-22
View File
@@ -1,22 +0,0 @@
FROM node:boron
# Upgrade NPM to v5 (Yarn is needed because of this bug https://github.com/npm/npm/issues/16807)
# The used solution is suggested here https://github.com/npm/npm/issues/16807#issuecomment-313591975
RUN yarn global add npm@5
# 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 --branch release 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 ["node", "./website/transpiled-babel/index.js"]
+3 -2
View File
@@ -5,7 +5,8 @@ Habitica [![Build Status](https://travis-ci.org/HabitRPG/habitica.svg?branch=dev
We need more programmers! Your assistance will be greatly appreciated.
For an introduction to the technologies used and how the software is organized, refer to [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths).
For an introduction to the technologies used and how the software is organized, refer to [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica#Coders_.28Web_.26_Mobile.29) - "Coders (Web & Mobile)" section.
To set up a local install of Habitica for development and testing on various platforms, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
To set up a local install of Habitica for development and testing, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally), which contains instructions for Windows, *nix / Mac OS, and Vagrant.
Then read [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths) for additional instructions and useful tips.
+1 -1
View File
@@ -30,7 +30,7 @@
"bootstrap-tour": "0.10.1",
"css-social-buttons": "samcollins/css-social-buttons#v1.1.1 ",
"github-buttons": "mdo/github-buttons#v3.0.0",
"hello": "1.14.1",
"hello": "1.13.4",
"jquery": "2.1.0",
"jquery-colorbox": "1.4.36",
"jquery-ui": "1.10.3",
+3 -18
View File
@@ -66,22 +66,16 @@
},
"mode":"sandbox",
"client_id":"client_id",
"client_secret":"client_secret",
"experience_profile_id": ""
"client_secret":"client_secret"
},
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
"LOGGLY_TOKEN": "token",
"LOGGLY_ACCOUNT": "account",
"PUSH_CONFIGS": {
"GCM_SERVER_API_KEY": "",
"APN_ENABLED": "false",
"APN_ENABLED": "true",
"FCM_SERVER_API_KEY": ""
},
"SITE_HTTP_AUTH": {
"ENABLED": "false",
"USERNAME": "admin",
"PASSWORD": "password"
},
"PUSHER": {
"ENABLED": "false",
"APP_ID": "appId",
@@ -93,14 +87,5 @@
"FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
"SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id"
},
"ITUNES_SHARED_SECRET": "aaaabbbbccccddddeeeeffff00001111",
"EMAILS" : {
"COMMUNITY_MANAGER_EMAIL" : "leslie@habitica.com",
"TECH_ASSISTANCE_EMAIL" : "admin@habitica.com",
"PRESS_ENQUIRY_EMAIL" : "leslie@habitica.com"
},
"LOGGLY" : {
"TOKEN" : "example-token",
"SUBDOMAIN" : "exmaple-subdomain"
}
"ITUNES_SHARED_SECRET": "aaaabbbbccccddddeeeeffff00001111"
}
@@ -3,7 +3,7 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
/**
* database_reports/count_users_who_own_specified_gear.js
* https://github.com/HabitRPG/habitica/pull/3884
* https://github.com/HabitRPG/habitrpg/pull/3884
*/
var thingsOfInterest = {
+1 -1
View File
@@ -1,3 +1,3 @@
web:
volumes:
- '.:/usr/src/habitrpg'
- '.:/habitrpg'
-36
View File
@@ -1,36 +0,0 @@
import gulp from 'gulp';
import fs from 'fs';
// Copy Bootstrap 4 config variables from /website /node_modules so we can check
// them into Git
const BOOSTRAP_NEW_CONFIG_PATH = 'website/client/assets/scss/bootstrap_config.scss';
const BOOTSTRAP_ORIGINAL_CONFIG_PATH = 'node_modules/bootstrap/scss/_custom.scss';
// https://stackoverflow.com/a/14387791/969528
function copyFile(source, target, cb) {
let cbCalled = false;
function done(err) {
if (!cbCalled) {
cb(err);
cbCalled = true;
}
}
let rd = fs.createReadStream(source);
rd.on('error', done);
let wr = fs.createWriteStream(target);
wr.on('error', done);
wr.on('close', () => done());
rd.pipe(wr);
}
gulp.task('bootstrap', (done) => {
// use new config
copyFile(
BOOSTRAP_NEW_CONFIG_PATH,
BOOTSTRAP_ORIGINAL_CONFIG_PATH,
done,
);
});
+2 -16
View File
@@ -1,7 +1,6 @@
import gulp from 'gulp';
import runSequence from 'run-sequence';
import babel from 'gulp-babel';
import webpackProductionBuild from '../webpack/build';
require('gulp-grunt')(gulp);
gulp.task('build', () => {
@@ -26,15 +25,7 @@ gulp.task('build:common', () => {
gulp.task('build:server', ['build:src', 'build:common']);
// Client Production Build
gulp.task('build:client', ['bootstrap'], (done) => {
webpackProductionBuild((err, output) => {
if (err) return done(err);
console.log(output);
});
});
gulp.task('build:dev', ['browserify', 'prepare:staticNewStuff'], (done) => {
gulp.task('build:dev', ['browserify', 'prepare:staticNewStuff', 'semantic-ui'], (done) => {
gulp.start('grunt-build:dev', done);
});
@@ -42,12 +33,7 @@ gulp.task('build:dev:watch', ['build:dev'], () => {
gulp.watch(['website/client-old/**/*.styl', 'website/common/script/*']);
});
gulp.task('build:prod', [
'browserify',
'build:server',
'prepare:staticNewStuff',
'build:client',
], (done) => {
gulp.task('build:prod', ['browserify', 'build:server', 'prepare:staticNewStuff', 'semantic-ui'], (done) => {
runSequence(
'grunt-build:prod',
'apidoc',
+43
View File
@@ -0,0 +1,43 @@
import gulp from 'gulp';
import fs from 'fs';
// Make semantic-ui-less work with a theme in a different folder
// Code taken from https://www.artembutusov.com/webpack-semantic-ui/
// Relative to node_modules/semantic-ui-less
const SEMANTIC_THEME_PATH = '../../website/client/assets/less/semantic-ui/theme.config';
// fix well known bug with default distribution
function fixFontPath (filename) {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (err, content) => {
if (err) return reject(err);
let newContent = content.replace(
'@fontPath : \'../../themes/',
'@fontPath : \'../../../themes/'
);
fs.writeFile(filename, newContent, 'utf8', (err1) => {
if (err) return reject(err1);
resolve();
});
});
});
}
gulp.task('semantic-ui', (done) => {
// relocate default config
fs.writeFile(
'node_modules/semantic-ui-less/theme.config',
`@import '${SEMANTIC_THEME_PATH}';\n`,
'utf8',
(err) => {
if (err) return done(err);
fixFontPath('node_modules/semantic-ui-less/themes/default/globals/site.variables')
.then(() => done())
.catch(done);
}
);
});
+1 -1
View File
@@ -49,7 +49,7 @@ gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSpri
});
if (numberOfSheetsThatAreTooBig > 0) {
console.error(`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle them, but there is a margin of error in these calculations so it is probably okay. Mention this to an admin so they can test a staging site on mobile Safari after your PR is merged.`); // https://github.com/HabitRPG/habitica/pull/6683#issuecomment-185462180
console.error(`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle them, but there is a margin of error in these calculations so it is probably okay. Mention this to an admin so they can test a staging site on mobile Safari after your PR is merged.`); // https://github.com/HabitRPG/habitrpg/pull/6683#issuecomment-185462180
} else {
console.log('All images are within the correct dimensions');
}
+2 -2
View File
@@ -280,7 +280,7 @@ gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => {
gulp.task('test:api-v3:unit', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-unit --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/unit --recursive --require ./test/helpers/start-server'),
testBin('mocha test/api/v3/unit --recursive --require ./test/helpers/start-server'),
(err, stdout, stderr) => {
if (err) {
process.exit(1);
@@ -298,7 +298,7 @@ gulp.task('test:api-v3:unit:watch', () => {
gulp.task('test:api-v3:integration', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server'),
{maxBuffer: 500 * 1024},
(err, stdout, stderr) => {
if (err) {
+6 -6
View File
@@ -67,7 +67,7 @@ gulp.task('transifex:malformedStrings', () => {
let stringsWithIncorrectNumberOfInterpolations = [];
let count = 0;
_.each(ALL_LANGUAGES, function (lang) {
_(ALL_LANGUAGES).each(function (lang) {
_.each(stringsToLookFor, function (strings, file) {
let translationFile = fs.readFileSync(LOCALES + lang + '/' + file);
@@ -89,7 +89,7 @@ gulp.task('transifex:malformedStrings', () => {
}
});
});
});
}).value();
if (!_.isEmpty(stringsWithMalformedInterpolations)) {
let message = 'The following strings have malformed or missing interpolations';
@@ -114,7 +114,7 @@ function getArrayOfLanguages () {
function eachTranslationFile (languages, cb) {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
_.each(languages, (lang) => {
_(languages).each((lang) => {
_.each(jsonFiles, (filename) => {
try {
var translationFile = fs.readFileSync(LOCALES + lang + '/' + filename);
@@ -128,7 +128,7 @@ function eachTranslationFile (languages, cb) {
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
});
});
}).value();
}
function eachTranslationString (languages, cb) {
@@ -153,7 +153,7 @@ function formatMessageForPosting (msg, items) {
function getStringsWith (json, interpolationRegex) {
var strings = {};
_.each(json, function (file_name) {
_(json).each(function (file_name) {
var raw_file = fs.readFileSync(ENGLISH_LOCALE + file_name);
var parsed_json = JSON.parse(raw_file);
@@ -162,7 +162,7 @@ function getStringsWith (json, interpolationRegex) {
var match = value.match(interpolationRegex);
if (match) strings[file_name][key] = match;
});
});
}).value();
return strings;
}
+1 -1
View File
@@ -9,11 +9,11 @@
require('babel-register');
if (process.env.NODE_ENV === 'production') {
require('./gulp/gulp-semanticui');
require('./gulp/gulp-apidoc');
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']);
-60
View File
@@ -1,60 +0,0 @@
# 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....
-25
View File
@@ -1,25 +0,0 @@
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
-14
View File
@@ -1,14 +0,0 @@
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
-22
View File
@@ -1,22 +0,0 @@
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
-13
View File
@@ -1,13 +0,0 @@
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
-28
View File
@@ -1,28 +0,0 @@
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
-7
View File
@@ -1,12 +1,5 @@
// %mongo server:27017/dbname underscore.js my_commands.js
// %mongo server:27017/dbname underscore.js --shell
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var habits = 0,
dailies = 0,
todos = 0,
-6
View File
@@ -3,12 +3,6 @@
*/
// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130326_migrate_pets.js
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var mapping = {
bearcub: {name:'BearCub', modifier: 'Base'},
cactus: {name:'Cactus', modifier:'Base'},
-6
View File
@@ -4,12 +4,6 @@
// mongo habitrpg ./node_modules/underscore/underscore.js migrations/20130327_apply_tokens.js
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var mapping = [
{
tier: 1,
@@ -6,11 +6,6 @@
* mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130508_fix_duff_party_subscriptions.js
*/
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
// since our primary subscription will first hit parties now, we *definitely* need an index there
db.parties.ensureIndex( { 'members': 1}, {background: true} );
-6
View File
@@ -1,11 +1,5 @@
//mongo habitrpg ./node_modules/lodash/lodash.js migrations/20130602_survey_rewards.js
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var members = []
members = _.uniq(members);
@@ -3,12 +3,6 @@
// Racer was notorious for adding duplicates, randomly deleting documents, etc. Once we pull the plug on old.habit,
// run this migration to cleanup all the corruption
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
db.users.find().forEach(function(user){
// remove corrupt tasks, which will either be null-value or no id
@@ -5,12 +5,6 @@
// @see http://stackoverflow.com/questions/14867697/mongoose-full-collection-scan
//Also, what do we think of a Mongoose Migration module? something like https://github.com/madhums/mongoose-migrate
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
db.users.find().forEach(function(user){
// Add invites to groups
@@ -8,12 +8,6 @@
var mongo = require('mongoskin');
var _ = require('lodash');
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var backupUsers = mongo.db('localhost:27017/habitrpg_old?auto_reconnect').collection('users');
var liveUsers = mongo.db('localhost:27017/habitrpg_new?auto_reconnect').collection('users');
-6
View File
@@ -1,11 +1,5 @@
// node .migrations/20131127_restore_dayStart.js
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var mongo = require('mongoskin');
var _ = require('lodash');
-6
View File
@@ -8,12 +8,6 @@ mongo = require('mongoskin')
_ = require('lodash')
async = require('async')
# IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
# We've now upgraded to lodash v4 but the code used in this migration has not been
# adapted to work with it. Before this migration is used again any lodash method should
# be checked for compatibility against the v4 changelog and changed if necessary.
# https://github.com/lodash/lodash/wiki/Changelog#v400
db = mongo.db('localhost:27017/habitrpg?auto_reconnect')
###
@@ -1,11 +1,5 @@
// node .migrations/20131221_restore_NaN_history.js
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
/**
* After the classes migration, users lost some history entries
*/
-6
View File
@@ -1,11 +1,5 @@
// node .migrations/20131225_restore_streaks.js
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
/**
* After the classes migration, users lost some history entries
*/
@@ -5,12 +5,6 @@ var migrationName = '20140823_remove_undefined_and_false_notifications';
var authorName = 'Alys'; // in case script author needs to know when their ...
var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
/**
* https://github.com/HabitRPG/habitrpg/pull/3907
*/
@@ -4,12 +4,6 @@ var migrationName = '20140829_change_headAccessory_to_eyewear';
var authorName = 'Alys'; // in case script author needs to know when their ...
var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
/**
* https://github.com/HabitRPG/habitrpg/issues/3645
*/
@@ -4,11 +4,6 @@
//
// node 20140831_increase_gems_for_previous_contributions.js > 20140831_increase_gems_for_previous_contributions_output.txt
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var migrationName = '20140831_increase_gems_for_previous_contributions';
@@ -8,12 +8,6 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
* Convert Tier 7 contributors with admin flag to Tier 8 (moderators).
*/
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var mongo = require('mongoskin');
var _ = require('lodash');
@@ -1,9 +1,3 @@
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
// require moment, lodash
db.users.find(
{'purchased.plan.customerId':{$ne:null}},
-6
View File
@@ -9,12 +9,6 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var mongo = require('mongoskin');
var _ = require('lodash');
@@ -8,12 +8,6 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var mongo = require('mongoskin');
var _ = require('lodash');
@@ -19,12 +19,6 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var mongo = require('mongoskin');
var _ = require('lodash');
var moment = require('moment');
@@ -6,12 +6,6 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
* force all active players to rest in the inn due to massive server fail
*/
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
var mongo = require('mongoskin');
-6
View File
@@ -19,12 +19,6 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
* means minimal new testing.
*/
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var dbserver = 'localhost:27017' // FOR TEST DATABASE
// var dbserver = 'username:password@ds031379-a0.mongolab.com:31379' // FOR PRODUCTION DATABASE
var dbname = 'habitrpg';
@@ -7,12 +7,6 @@ var migrationName = '20160111_challenges_condense_same_day_history_entries.js';
var dbserver = '';
var dbname = '';
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var mongo = require('mongoskin');
var _ = require('lodash');
var moment = require('moment');
-6
View File
@@ -11,12 +11,6 @@ var dbserver = 'localhost:27017'; // FOR TEST DATABASE
// var dbserver = 'username:password@ds031379-a0.mongolab.com:31379'; // FOR PRODUCTION DATABASE
var dbname = 'habitrpg';
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var mongo = require('mongoskin');
var _ = require('lodash');
-6
View File
@@ -14,12 +14,6 @@ var dbname = 'habitrpg';
var mongo = require('mongoskin');
var _ = require('lodash');
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var dbUsers = mongo.db(dbserver + '/' + dbname + '?auto_reconnect').collection('users');
// specify a query to limit the affected users (empty for all users):
@@ -2,12 +2,6 @@ var uuid = require('uuid').v4;
var mongo = require('mongodb').MongoClient;
var _ = require('lodash');
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
var taskIds = require('checklists-no-id.json').map(function (obj) {
return obj._id;
});
@@ -1,110 +0,0 @@
'use strict';
/****************************************
* Author: @Alys
*
* Reason: Collection quests are being changed
* to require fewer items collected:
* https://github.com/HabitRPG/habitrpg/pull/7987
* This will cause existing quests to end sooner
* than the party is expecting.
* This script inserts an explanatory `system`
* message into the chat for affected parties.
***************************************/
global.Promise = require('bluebird');
const uuid = require('uuid');
const TaskQueue = require('cwait').TaskQueue;
const logger = require('./utils/logger');
const Timer = require('./utils/timer');
const connectToDb = require('./utils/connect').connectToDb;
const closeDb = require('./utils/connect').closeDb;
const message = '`This party\'s collection quest has been made easier! For details, refer to http://habitica.wikia.com/wiki/User_blog:LadyAlys/Collection_Quests_are_Now_Easier`';
const timer = new Timer();
// PROD: Enable prod db
// const DB_URI = 'mongodb://username:password@dsXXXXXX-a0.mlab.com:XXXXX,dsXXXXXX-a1.mlab.com:XXXXX/habitica?replicaSet=rs-dsXXXXXX';
const DB_URI = 'mongodb://localhost/habitrpg';
const COLLECTION_QUESTS = [
'vice2',
'egg',
'moonstone1',
'goldenknight1',
'dilatoryDistress1',
];
let Groups;
connectToDb(DB_URI).then((db) => {
Groups = db.collection('groups');
return Promise.resolve();
})
.then(findPartiesWithCollectionQuest)
// .then(displayGroups) // for testing only
.then(addMessageToGroups)
.then(() => {
timer.stop();
closeDb();
}).catch(reportError);
function reportError (err) {
logger.error('Uh oh, an error occurred');
closeDb();
timer.stop();
throw err;
}
function findPartiesWithCollectionQuest () {
logger.info('Looking up groups on collection quests...');
return Groups.find({'quest.key': {$in: COLLECTION_QUESTS}}, ['name','quest']).toArray().then((groups) => {
logger.success('Found', groups.length, 'parties on collection quests');
return Promise.resolve(groups);
})
}
function displayGroups (groups) { // for testing only
logger.info('Displaying parties...');
console.log(groups);
return Promise.resolve(groups);
}
function updateGroupById (group) {
var newMessage = {
'id' : uuid.v4(),
'text' : message,
'timestamp': Date.now(),
'likes': {},
'flags': {},
'flagCount': 0,
'uuid': 'system'
};
return Groups.findOneAndUpdate({_id: group._id}, {$push:{"chat" :{$each: [newMessage], $position:0}}}, {returnOriginal: false});
// Does not set the newMessage flag for all party members because I don't think it's essential and
// I don't want to run the extra code (extra database load, extra opportunity for bugs).
}
function addMessageToGroups (groups) {
let queue = new TaskQueue(Promise, 300);
logger.info('About to update', groups.length, 'parties...');
return Promise.map(groups, queue.wrap(updateGroupById)).then((result) => {
let updates = result.filter(res => res.lastErrorObject && res.lastErrorObject.updatedExisting)
let failures = result.filter(res => !(res.lastErrorObject && res.lastErrorObject.updatedExisting));
logger.success(updates.length, 'parties have been notified');
if (failures.length > 0) {
logger.error(failures.length, 'parties could not be notified');
}
return Promise.resolve();
});
}
@@ -1,88 +0,0 @@
var migrationName = '20170418_subscriber_jackalopes.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 Jackalope pet to all current subscribers
*/
var monk = require('monk');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = monk(connectionString).get('users', { castIds: false });
var now = new Date();
function processUsers(lastId) {
// specify a query to limit the affected users (empty for all users):
var query = {
'purchased.plan.customerId': {$type: 2},
$or: [
{'purchased.plan.dateTerminated': null},
{'purchased.plan.dateTerminated': {$exists: false}},
{'purchased.plan.dateTerminated': {$gt: now}},
]
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch(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 = {'items.pets.Jackalope-RoyalPurple': 5};
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;
-207
View File
@@ -1,207 +0,0 @@
var migrationName = '20170425_missing_incentives';
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
/*
* Award missing Royal Purple Hatching Potion to users with 55+ check-ins
* Reduce users with impossible check-in counts to a reasonable number
*/
import monk from 'monk';
import common from '../website/common';
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 = {
'loginIncentives': {$gt:99},
'migration': {$ne: migrationName},
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch(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 language = user.preferences.language || 'en';
var set = {'migration': migrationName};
var inc = {
'items.eggs.BearCub': 0,
'items.eggs.Cactus': 0,
'items.eggs.Dragon': 0,
'items.eggs.FlyingPig': 0,
'items.eggs.Fox': 0,
'items.eggs.LionCub': 0,
'items.eggs.PandaCub': 0,
'items.eggs.TigerCub': 0,
'items.eggs.Wolf': 0,
'items.food.Chocolate': 0,
'items.food.CottonCandyBlue': 0,
'items.food.CottonCandyPink': 0,
'items.food.Fish': 0,
'items.food.Honey': 0,
'items.food.Meat': 0,
'items.food.Milk': 0,
'items.food.Potatoe': 0,
'items.food.RottenMeat': 0,
'items.food.Strawberry': 0,
'items.hatchingPotions.Base': 0,
'items.hatchingPotions.CottonCandyBlue': 0,
'items.hatchingPotions.CottonCandyPink': 0,
'items.hatchingPotions.Desert': 0,
'items.hatchingPotions.Golden': 0,
'items.hatchingPotions.Red': 0,
'items.hatchingPotions.RoyalPurple': 0,
'items.hatchingPotions.Shade': 0,
'items.hatchingPotions.Skeleton': 0,
'items.hatchingPotions.White': 0,
'items.hatchingPotions.Zombie': 0,
};
var nextReward;
if (user.loginIncentives >= 105) {
inc['items.hatchingPotions.RoyalPurple'] += 1;
nextReward = 110;
}
if (user.loginIncentives >= 110) {
inc['items.eggs.BearCub'] += 1;
inc['items.eggs.Cactus'] += 1;
inc['items.eggs.Dragon'] += 1;
inc['items.eggs.FlyingPig'] += 1;
inc['items.eggs.Fox'] += 1;
inc['items.eggs.LionCub'] += 1;
inc['items.eggs.PandaCub'] += 1;
inc['items.eggs.TigerCub'] += 1;
inc['items.eggs.Wolf'] += 1;
nextReward = 115;
}
if (user.loginIncentives >= 115) {
inc['items.hatchingPotions.RoyalPurple'] += 1;
nextReward = 120;
}
if (user.loginIncentives >= 120) {
inc['items.hatchingPotions.Base'] += 1;
inc['items.hatchingPotions.CottonCandyBlue'] += 1;
inc['items.hatchingPotions.CottonCandyPink'] += 1;
inc['items.hatchingPotions.Desert'] += 1;
inc['items.hatchingPotions.Golden'] += 1;
inc['items.hatchingPotions.Red'] += 1;
inc['items.hatchingPotions.Shade'] += 1;
inc['items.hatchingPotions.Skeleton'] += 1;
inc['items.hatchingPotions.White'] += 1;
inc['items.hatchingPotions.Zombie'] += 1;
nextReward = 125;
}
if (user.loginIncentives >= 125) {
inc['items.hatchingPotions.RoyalPurple'] += 1;
nextReward = 130;
}
if (user.loginIncentives >= 130) {
inc['items.food.Chocolate'] += 3;
inc['items.food.CottonCandyBlue'] += 3;
inc['items.food.CottonCandyPink'] += 3;
inc['items.food.Fish'] += 3;
inc['items.food.Honey'] += 3;
inc['items.food.Meat'] += 3;
inc['items.food.Milk'] += 3;
inc['items.food.Potatoe'] += 3;
inc['items.food.RottenMeat'] += 3;
inc['items.food.Strawberry'] += 3;
}
if (user.loginIncentives >= 135) {
inc['items.hatchingPotions.RoyalPurple'] += 1;
nextReward = 140;
}
if (user.loginIncentives >= 140) {
set['items.gear.owned.weapon_special_skeletonKey'] = true;
set['items.gear.owned.shield_special_lootBag'] = true;
nextReward = 145;
}
if (user.loginIncentives >= 145) {
inc['items.hatchingPotions.RoyalPurple'] += 1;
nextReward = 150;
}
if (user.loginIncentives >= 150) {
set['items.gear.owned.head_special_clandestineCowl'] = true;
set['items.gear.owned.armor_special_sneakthiefRobes'] = true;
nextReward = 155;
}
if (user.loginIncentives > 155) {
set.loginIncentives = 155;
nextReward = 160;
}
var push = {
'notifications': {
'type': 'LOGIN_INCENTIVE',
'data': {
'nextRewardAt': nextReward,
'rewardKey': [
'shop_armoire',
],
'rewardText': common.i18n.t('checkInRewards', language),
'reward': [],
'message': common.i18n.t('backloggedCheckInRewards', language),
},
'id': common.uuid(),
}
};
dbUsers.update({_id: user._id}, {$set:set, $push:push, $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;
-114
View File
@@ -1,114 +0,0 @@
var migrationName = '20170616_achievements';
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
/*
* Updates to achievements for June 16, 2017 biweekly merge
* 1. Multiply various collection quest achievements based on difficulty reduction
* 2. Award Joined Challenge achievement to those who should have it already
*/
import monk from '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 = {
$or: [
{'achievements.quests.dilatoryDistress1': {$gt:0}},
{'achievements.quests.egg': {$gt:0}},
{'achievements.quests.goldenknight1': {$gt:0}},
{'achievements.quests.moonstone1': {$gt:0}},
{'achievements.quests.vice2': {$gt:0}},
{'achievements.challenges': {$exists: true, $ne: []}},
{'challenges': {$exists: true, $ne: []}},
],
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [ // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
'achievements',
'challenges',
],
})
.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 = {'migration': migrationName};
if (user.challenges.length > 0 || user.achievements.challenges.length > 0) {
set['achievements.joinedChallenge'] = true;
}
if (user.achievements.quests.dilatoryDistress1) {
set['achievements.quests.dilatoryDistress1'] = Math.ceil(user.achievements.quests.dilatoryDistress1 * 1.25);
}
if (user.achievements.quests.egg) {
set['achievements.quests.egg'] = Math.ceil(user.achievements.quests.egg * 2.5);
}
if (user.achievements.quests.goldenknight1) {
set['achievements.quests.goldenknight1'] = user.achievements.quests.goldenknight1 * 5;
}
if (user.achievements.quests.moonstone1) {
set['achievements.quests.moonstone1'] = user.achievements.quests.moonstone1 * 5;
}
if (user.achievements.quests.vice2) {
set['achievements.quests.vice2'] = Math.ceil(user.achievements.quests.vice2 * 1.5);
}
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;
-90
View File
@@ -1,90 +0,0 @@
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;
-109
View File
@@ -1,109 +0,0 @@
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;
-97
View File
@@ -1,97 +0,0 @@
var migrationName = '20170928_redesign_guilds.js';
/*
* Copy Guild Leader messages to end of Guild descriptions
* Copy Guild logos to beginning of Guild descriptions
*/
var monk = require('monk');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbGroups = monk(connectionString).get('groups', { castIds: false });
function processGroups(lastId) {
// specify a query to limit the affected groups (empty for all groups):
var query = {
};
var fields = {
'description': 1,
'logo': 1,
'leaderMessage': 1,
}
if (lastId) {
query._id = {
$gt: lastId
}
}
return dbGroups.find(query, {
fields: fields,
sort: {_id: 1},
limit: 250,
})
.then(updateGroups)
.catch(function (err) {
console.log(err);
return exiting(1, 'ERROR! ' + err);
});
}
var progressCount = 1000;
var count = 0;
function updateGroups (groups) {
if (!groups || groups.length === 0) {
console.warn('All appropriate groups found and modified.');
displayData();
return;
}
var groupPromises = groups.map(updateGroup);
var lastGroup = groups[groups.length - 1];
return Promise.all(groupPromises)
.then(function () {
processGroups(lastGroup._id);
});
}
function updateGroup (group) {
count++;
var description = group.description;
if (group.logo) {
description = '![Guild Logo](' + group.logo + ')\n\n&nbsp;\n\n' + description;
}
if (group.leaderMessage) {
description = description + '\n\n&nbsp;\n\n' + group.leaderMessage;
}
var set = {
description: description,
};
if (count % progressCount == 0) console.warn(count + ' ' + group._id);
return dbGroups.update({_id: group._id}, {$set:set});
}
function displayData() {
console.warn('\n' + count + ' groups 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 = processGroups;
-127
View File
@@ -1,127 +0,0 @@
var updateStore = require('../website/common/script/libs/updateStore');
var getItemInfo = require('../website/common/script/libs/getItemInfo');
var migrationName = '20170928_redesign_launch.js';
var authorName = 'paglias'; // in case script author needs to know when their ...
var authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; //... own data is done
/*
* Migrate existing in app rewards lists to pinned items
* Award Veteran Pets
*/
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-09-21')},
};
var fields = {
'items.pets': 1,
'items.gear': 1,
'stats.class': 1,
}
if (lastId) {
query._id = {
$gt: lastId
}
}
return dbUsers.find(query, {
fields: fields,
sort: {_id: 1},
limit: 250,
})
.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 = {'migration': migrationName};
var oldRewardsList = updateStore(user);
var newPinnedItems = [
{
type: 'armoire',
path: 'armoire',
},
{
type: 'potion',
path: 'potion',
},
];
oldRewardsList.forEach(item => {
var type = 'marketGear';
var itemInfo = getItemInfo(user, 'marketGear', item);
newPinnedItems.push({
type,
path: itemInfo.path,
})
});
set.pinnedItems = newPinnedItems;
if (user.items.pets['Lion-Veteran']) {
set['items.pets.Bear-Veteran'] = 5;
} else if (user.items.pets['Tiger-Veteran']) {
set['items.pets.Lion-Veteran'] = 5;
} else if (user.items.pets['Wolf-Veteran']) {
set['items.pets.Tiger-Veteran'] = 5;
} else {
set['items.pets.Wolf-Veteran'] = 5;
}
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
return dbUsers.update({_id: user._id}, {$set:set});
}
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;
-6
View File
@@ -6,12 +6,6 @@
// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB).
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
console.log('Starting migrations/api_v3/challenges.js.');
require('babel-register');
-6
View File
@@ -9,12 +9,6 @@
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
console.log('Starting migrations/api_v3/challengesMembers.js.');
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
require('babel-register');
require('babel-polyfill');
-6
View File
@@ -8,12 +8,6 @@
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
console.log('Starting migrations/api_v3/coupons.js.');
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
require('babel-register');
require('babel-polyfill');
@@ -8,12 +8,6 @@
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
console.log('Starting migrations/api_v3/unsubscriptions.js.');
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
require('babel-register');
require('babel-polyfill');
-6
View File
@@ -16,12 +16,6 @@
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
console.log('Starting migrations/api_v3/groups.js.');
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
require('babel-register');
require('babel-polyfill');
-6
View File
@@ -9,12 +9,6 @@
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
console.log('Starting migrations/api_v3/users.js.');
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
// We've now upgraded to lodash v4 but the code used in this migration has not been
// adapted to work with it. Before this migration is used again any lodash method should
// be checked for compatibility against the v4 changelog and changed if necessary.
// https://github.com/lodash/lodash/wiki/Changelog#v400
require('babel-register');
require('babel-polyfill');
@@ -1,47 +0,0 @@
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;
+1 -1
View File
@@ -7,6 +7,6 @@
db.users.find().forEach(function(user){
user.tasks = user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards);
var found = _.some(user.tasks, {text: ""})
var found = _.any(user.tasks, {text: ""})
if (found) printjson({id:user._id, auth:user.auth});
})
@@ -1,40 +0,0 @@
var migrationName = 'AddUnlimitedSubscription';
var authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
var authorUuid = ''; //... own data is done
/*
* This migrations will add a free subscription to a specified group
*/
import moment from 'moment';
import { model as Group } from '../../website/server/models/group';
// @TODO: this should probably be a GroupManager library method
async function addUnlimitedSubscription (groupId, dateTerminated) {
let group = await Group.findById(groupId);
group.purchased.plan.customerId = "group-unlimited";
group.purchased.plan.dateCreated = new Date();
group.purchased.plan.dateUpdated = new Date();
group.purchased.plan.paymentMethod = "Group Unlimited";
group.purchased.plan.planId = "group_monthly";
group.purchased.plan.dateTerminated = null;
if (dateTerminated) {
let dateToEnd = moment(dateTerminated).toDate();
group.purchased.plan.dateTerminated = dateToEnd;
}
// group.purchased.plan.owner = ObjectId();
group.purchased.plan.subscriptionId = "";
return group.save();
};
module.exports = async function addUnlimitedSubscriptionCreator () {
let groupId = process.argv[2];
if (!groupId) throw Error('Group ID is required');
let dateTerminated = process.argv[3];
let result = await addUnlimitedSubscription(groupId, dateTerminated);
};
-32
View File
@@ -1,32 +0,0 @@
import Bluebird from 'bluebird';
import { model as Group } from '../../website/server/models/group';
import { model as User } from '../../website/server/models/user';
// @TODO: this should probably be a GroupManager library method
async function createGroup (name, privacy, type, leaderId) {
let user = await User.findById(leaderId);
let group = new Group({
name,
privacy,
type,
});
group.leader = user._id;
user.guilds.push(group._id);
return Bluebird.all([group.save(), user.save()]);
};
module.exports = async function groupCreator () {
let name = process.argv[2];
let privacy = process.argv[3];
let type = process.argv[4];
let leaderId = process.argv[5];
let result = await createGroup(name, privacy, type, leaderId)
};
-46
View File
@@ -1,46 +0,0 @@
var migrationName = 'Jackalopes for Unlimited Subscribers';
/*
* This migration will find users with unlimited subscriptions who are also eligible for Jackalope mounts, and award them
*/
import Bluebird from 'bluebird';
import { model as Group } from '../../website/server/models/group';
import { model as User } from '../../website/server/models/user';
import * as payments from '../../website/server/libs/payments';
async function handOutJackalopes () {
let promises = [];
let cursor = User.find({
'purchased.plan.customerId':'habitrpg',
}).cursor();
cursor.on('data', async function(user) {
console.log('User: ' + user._id);
let groupList = [];
if (user.party._id) groupList.push(user.party._id);
groupList = groupList.concat(user.guilds);
let subscribedGroup =
await Group.findOne({
'_id': {$in: groupList},
'purchased.plan.planId': 'group_monthly',
'purchased.plan.dateTerminated': null,
},
{'_id':1}
);
if (subscribedGroup) {
User.update({'_id':user._id},{$set:{'items.mounts.Jackalope-RoyalPurple':true}}).exec();
promises.push(user.save());
}
});
cursor.on('close', async function() {
console.log('done');
return await Bluebird.all(promises);
});
};
module.exports = handOutJackalopes;
@@ -1,33 +0,0 @@
var migrationName = 'ResyncGroupPlanMembers';
var authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
var authorUuid = ''; //... own data is done
/*
* This migrations will iterate through all groups with a group plan a subscription and resync the free
* subscription to all members
*/
import Bluebird from 'bluebird';
import { model as Group } from '../../website/server/models/group';
import * as payments from '../../website/server/libs/payments';
async function updateGroupsWithGroupPlans () {
let cursor = Group.find({
'purchased.plan.planId': 'group_monthly',
'purchased.plan.dateTerminated': null,
}).cursor();
let promises = [];
cursor.on('data', function(group) {
promises.push(payments.addSubscriptionToGroupUsers(group));
promises.push(group.save())
});
cursor.on('close', async function() {
return await Bluebird.all(promises);
});
};
module.exports = updateGroupsWithGroupPlans;
-3
View File
@@ -1,9 +1,6 @@
// EMAIL="x@y.com" node ./migrations/manual_password_reset.js
// Be sure to have PRODUCTION_DB in your config.json
// IMPORTANT: this script isn't updated to use the new password encryption that uses bcrypt
// using it will break accounts and should not be used until upgraded
var nconf = require('nconf'),
path = require('path');
nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.json')));
+21 -24
View File
@@ -1,24 +1,21 @@
require("babel-register");
require("babel-polyfill");
// This file must use ES5, everything required can be in ES6
function setUpServer () {
var nconf = require('nconf');
var mongoose = require('mongoose');
var Bluebird = require('bluebird');
var setupNconf = require('../website/server/libs/setupNconf');
setupNconf();
// We require src/server and npt src/index because
// 1. nconf is already setup
// 2. we don't need clustering
require('../website/server/server'); // eslint-disable-line global-require
}
setUpServer();
// Replace this with your migration
var processUsers = require('./groups/update-groups-with-group-plans');
processUsers()
.catch(function (err) {
console.log(err)
})
require("babel-register");
require("babel-polyfill");
// This file must use ES5, everything required can be in ES6
function setUpServer () {
var nconf = require('nconf');
var mongoose = require('mongoose');
var Bluebird = require('bluebird');
var setupNconf = require('../website/server/libs/setupNconf');
setupNconf();
// We require src/server and npt src/index because
// 1. nconf is already setup
// 2. we don't need clustering
require('../website/server/server'); // eslint-disable-line global-require
}
setUpServer();
// Replace this with your migration
var processUsers = require('./new_stuff');
processUsers();
+1 -1
View File
@@ -2,7 +2,7 @@ var _id = '';
var update = {
$addToSet: {
'purchased.plan.mysteryItems':{
$each:['shield_mystery_201709','back_mystery_201709']
$each:['head_mystery_201702','back_mystery_201702']
}
}
};
-98
View File
@@ -1,98 +0,0 @@
let Bluebird = require('bluebird');
let request = require('superagent');
let last = require('lodash/last');
let AWS = require('aws-sdk');
let config = require('../config');
const S3_DIRECTORY = 'mobileApp/images'; //config.S3.SPRITES_DIRECTORY;
AWS.config.update({
accessKeyId: config.S3.accessKeyId,
secretAccessKey: config.S3.secretAccessKey,
// region: config.get('S3_REGION'),
});
let BUCKET_NAME = config.S3.bucket;
let s3 = new AWS.S3();
// Adapted from http://stackoverflow.com/a/22210077/2601552
function uploadFile (buffer, fileName) {
return new Promise((resolve, reject) => {
s3.putObject({
Body: buffer,
Key: fileName,
Bucket: BUCKET_NAME,
}, (error) => {
if (error) {
reject(error);
} else {
// console.info(`${fileName} uploaded to ${BUCKET_NAME} succesfully.`);
resolve(fileName);
}
});
});
}
function getFileName (file) {
let piecesOfPath = file.split('/');
let name = last(piecesOfPath);
let fullName = S3_DIRECTORY + name;
return fullName;
}
function getFileFromUrl (url) {
return new Promise((resolve, reject) => {
request.get(url).end((err, res) => {
if (err) return reject(err);
let file = res.body;
resolve(file);
});
});
}
let commit = '78f94e365c72cc58f66857d5941105638db7d35c';
commit = 'df0dbaba636c9ce424cc7040f7bd7fc1aa311015';
let gihuburl = `https://api.github.com/repos/HabitRPG/habitica/commits/${commit}`
let currentIndex = 0;
function uploadToS3(start, end, filesUrls) {
let urls = filesUrls.slice(start, end);
if (urls.length === 0) {
console.log("done");
return;
}
let promises = urls.map(fullUrl => {
return getFileFromUrl(fullUrl)
.then((buffer) => {
return uploadFile(buffer, getFileName(fullUrl));
});
});
console.log(promises.length)
return Bluebird.all(promises)
.then(() => {
currentIndex += 50;
uploadToS3(currentIndex, currentIndex + 50, filesUrls);
})
.catch(e => {
console.log(e);
});
}
request.get(gihuburl)
.end((err, res) => {
console.log(err);
let files = res.body.files;
let filesUrls = [''];
filesUrls = files.map(file => {
return file.raw_url;
})
uploadToS3(currentIndex, currentIndex + 50, filesUrls);
});
+2 -2
View File
@@ -1,4 +1,4 @@
var migrationName = '20170502_takeThis.js'; // Update per month
var migrationName = '20170201_takeThis.js'; // Update per month
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
@@ -14,7 +14,7 @@ function processUsers(lastId) {
// specify a query to limit the affected users (empty for all users):
var query = {
'migration':{$ne:migrationName},
'challenges':{$in:['69999331-d4ea-45a0-8c3f-f725d22b56c8']} // Update per month
'challenges':{$in:['b1d436b5-c784-42e3-9b07-7072479a6f8e']} // Update per month
};
if (lastId) {
@@ -1,83 +0,0 @@
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;
-109
View File
@@ -1,109 +0,0 @@
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();
};
+13195
View File
File diff suppressed because it is too large Load Diff
-21115
View File
File diff suppressed because it is too large Load Diff
+30 -46
View File
@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.0.2",
"version": "3.78.0",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",
@@ -13,12 +13,10 @@
"async": "^1.5.0",
"autoprefixer": "^6.4.0",
"aws-sdk": "^2.0.25",
"axios": "^0.16.0",
"axios": "^0.15.3",
"babel-core": "^6.0.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^6.0.0",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-async-to-module-method": "^6.8.0",
"babel-plugin-transform-object-rest-spread": "^6.16.0",
"babel-plugin-transform-regenerator": "^6.16.1",
@@ -30,22 +28,18 @@
"bcrypt": "^1.0.2",
"bluebird": "^3.3.5",
"body-parser": "^1.15.0",
"bootstrap": "4.0.0-alpha.6",
"bootstrap-vue": "^1.0.0-beta.6",
"bower": "~1.3.12",
"browserify": "~12.0.1",
"compression": "^1.6.1",
"connect-ratelimit": "0.0.7",
"cookie-session": "^1.2.0",
"coupon-code": "^0.4.5",
"cross-env": "^4.0.0",
"css-loader": "^0.28.0",
"css-loader": "^0.26.1",
"csv-stringify": "^1.0.2",
"cwait": "~1.0.1",
"cwait": "^1.0.0",
"domain-middleware": "~0.1.0",
"estraverse": "^4.1.1",
"express": "~4.14.0",
"express-basic-auth": "^1.0.1",
"express-csv": "~0.6.0",
"express-validator": "^2.18.0",
"extract-text-webpack-plugin": "^2.0.0-rc.3",
@@ -60,7 +54,7 @@
"grunt-contrib-stylus": "~0.20.0",
"grunt-contrib-uglify": "~0.6.0",
"grunt-contrib-watch": "~0.6.1",
"grunt-hashres": "git://github.com/habitrpg/grunt-hashres#dc85db6d3002e29e1b7c5ee186b80d708d2f0e0b",
"grunt-hashres": "~0.4.1",
"gulp": "^3.9.0",
"gulp-babel": "^6.1.2",
"gulp-grunt": "^0.5.2",
@@ -70,26 +64,26 @@
"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",
"intro.js": "^2.6.0",
"jade": "~1.11.0",
"jquery": ">=3.0.0",
"jquery": "^3.1.1",
"js2xmlparser": "~1.0.0",
"lodash": "^4.17.4",
"less": "^2.7.1",
"less-loader": "^2.2.3",
"lodash": "^3.10.1",
"lodash.pickby": "^4.2.0",
"lodash.setwith": "^4.2.0",
"merge-stream": "^1.0.0",
"method-override": "^2.3.5",
"moment": "^2.13.0",
"moment-recur": "git://github.com/habitrpg/moment-recur#f147ef27bbc26ca67638385f3db4a44084c76626",
"mongoose": "~4.8.6",
"mongoose": "^4.7.1",
"mongoose-id-autoinc": "~2013.7.14-4",
"morgan": "^1.7.0",
"nconf": "~0.8.2",
"nib": "^1.1.0",
"node-gcm": "^0.14.4",
"node-sass": "^4.5.0",
"nodemailer": "^2.3.2",
"object-path": "^0.9.2",
"ora": "^1.1.0",
@@ -99,26 +93,21 @@
"passport-google-oauth20": "1.0.0",
"paypal-ipn": "3.0.0",
"paypal-rest-sdk": "^1.2.1",
"popper.js": "^1.11.0",
"postcss-easy-import": "^2.0.0",
"pretty-data": "^0.40.0",
"ps-tree": "^1.0.0",
"pug": "^2.0.0-beta.12",
"push-notify": "git://github.com/habitrpg/push-notify#6bc2b5fdb1bdc9649b9ec1964d79ca50187fc8a9",
"pug": "^2.0.0-beta11",
"push-notify": "habitrpg/push-notify#v1.2.0",
"pusher": "^1.3.0",
"request": "~2.74.0",
"rimraf": "^2.4.3",
"run-sequence": "^1.1.4",
"s3-upload-stream": "^1.0.6",
"sass-loader": "^6.0.2",
"semantic-ui-less": "~2.2.4",
"serve-favicon": "^2.3.0",
"shelljs": "^0.7.6",
"sortablejs": "^1.6.1",
"stripe": "^4.2.0",
"superagent": "^3.4.3",
"svg-inline-loader": "^0.7.1",
"svg-url-loader": "^2.0.2",
"svgo-loader": "^1.2.1",
"universal-analytics": "~0.3.2",
"url-loader": "^0.5.7",
"useragent": "^2.1.9",
@@ -128,33 +117,30 @@
"vinyl-source-stream": "^1.1.0",
"vue": "^2.1.0",
"vue-loader": "^11.0.0",
"vue-mugen-scroll": "^0.2.1",
"vue-router": "^2.0.0-rc.5",
"vue-style-loader": "^3.0.0",
"vue-style-loader": "^2.0.0",
"vue-template-compiler": "^2.1.10",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker#45e607a7bccf4e3e089761b3b7b33e3f2c5dc21f",
"webpack": "^2.2.1",
"webpack-merge": "^4.0.0",
"webpack-merge": "^2.6.1",
"winston": "^2.1.0",
"winston-loggly-bulk": "^1.4.2",
"xml2js": "^0.4.4"
},
"private": true,
"engines": {
"node": "^6.9.1",
"npm": "^5.0.0"
"npm": "^4.0.2"
},
"scripts": {
"lint": "eslint --ext .js,.vue .",
"test": "npm run lint && gulp test && npm run client:unit && gulp apidoc",
"test": "npm run lint && gulp test && npm run client:unit",
"test:build": "gulp test:prepare:build",
"test:api-v3": "gulp test:api-v3",
"test:api-v3:unit": "gulp test:api-v3:unit",
"test:api-v3:integration": "gulp test:api-v3:integration",
"test:api-v3:integration:separate-server": "NODE_ENV=test gulp test:api-v3:integration:separate-server",
"test:sanity": "istanbul cover --dir coverage/sanity --report lcovonly node_modules/mocha/bin/_mocha -- test/sanity --recursive",
"test:common": "istanbul cover --dir coverage/common --report lcovonly node_modules/mocha/bin/_mocha -- test/common --recursive",
"test:content": "istanbul cover --dir coverage/content --report lcovonly node_modules/mocha/bin/_mocha -- test/content --recursive",
"test:sanity": "mocha test/sanity --recursive",
"test:common": "mocha test/common --recursive",
"test:content": "mocha test/content --recursive",
"test:karma": "karma start test/client-old/spec/karma.conf.js --single-run",
"test:karma:watch": "karma start test/client-old/spec/karma.conf.js",
"test:prepare:webdriver": "webdriver-manager update",
@@ -163,15 +149,14 @@
"test:nodemon": "gulp test:nodemon",
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
"sprites": "gulp sprites:compile",
"client:dev": "gulp bootstrap && node webpack/dev-server.js",
"client:build": "gulp build:client",
"client:dev": "node webpack/dev-server.js",
"client:build": "node webpack/build.js",
"client:unit": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js --single-run",
"client:unit:watch": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js",
"client:e2e": "node test/client/e2e/runner.js",
"client:test": "npm run client:unit && npm run client:e2e",
"start": "gulp run:dev",
"postinstall": "gulp build",
"apidoc": "gulp apidoc"
"postinstall": "bower --config.interactive=false install -f; gulp build; npm run client:build"
},
"devDependencies": {
"babel-plugin-istanbul": "^4.0.0",
@@ -181,11 +166,12 @@
"chromedriver": "^2.27.2",
"connect-history-api-fallback": "^1.1.0",
"coveralls": "^2.11.2",
"cross-env": "^3.1.4",
"cross-spawn": "^5.0.1",
"csv": "~0.3.6",
"deep-diff": "~0.1.4",
"eslint": "^3.0.0",
"eslint-config-habitrpg": "^3.0.0",
"eslint-config-habitrpg": "^2.0.0",
"eslint-friendly-formatter": "^2.0.5",
"eslint-loader": "^1.3.0",
"eslint-plugin-html": "^2.0.0",
@@ -196,7 +182,7 @@
"grunt-karma": "~0.12.1",
"http-proxy-middleware": "^0.17.0",
"inject-loader": "^3.0.0-beta4",
"istanbul": "^1.1.0-alpha.1",
"istanbul": "^0.3.14",
"karma": "^1.3.0",
"karma-babel-preprocessor": "^6.0.1",
"karma-chai-plugins": "~0.6.0",
@@ -204,21 +190,19 @@
"karma-mocha": "^0.2.0",
"karma-mocha-reporter": "^1.1.1",
"karma-phantomjs-launcher": "^1.0.0",
"karma-sinon-chai": "~1.2.0",
"karma-sinon-stub-promise": "^1.0.0",
"karma-sinon-chai": "^1.2.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.24",
"karma-webpack": "^2.0.2",
"lcov-result-merger": "^1.0.2",
"lolex": "^1.4.0",
"mocha": "^3.2.0",
"mocha": "^2.3.3",
"mongodb": "^2.0.46",
"mongoskin": "~2.1.0",
"monk": "^4.0.0",
"nightwatch": "^0.9.12",
"phantomjs-prebuilt": "^2.1.12",
"protractor": "^3.1.1",
"raw-loader": "^0.5.1",
"require-again": "^2.0.0",
"rewire": "^2.3.3",
"selenium-server": "^3.0.1",
+2 -19
View File
@@ -1,5 +1,3 @@
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),
@@ -9,10 +7,10 @@ var path = require('path');
var nconf = require('nconf');
var _ = require('lodash');
var paypal = require('paypal-rest-sdk');
var blocks = require('../website/common').content.subscriptionBlocks;
var blocks = require('../../../../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
@@ -51,8 +49,6 @@ _.each(blocks, function(block){
});
})
// @TODO: Add cli library for this
switch(OP) {
case "list":
paypal.billingPlan.list({status: 'ACTIVE'}, function(err, plans){
@@ -95,17 +91,4 @@ 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;
}
-1
View File
@@ -1 +0,0 @@
For information about writing and running tests, see [Using Your Local Install to Modify Habitica's Website and API](http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API).
+123
View File
@@ -0,0 +1,123 @@
# So you want to write API integration tests?
@TODO rewrite
That's great! This README will serve as a quick primer for style conventions and practices for these tests.
## What is this?
These are integration tests for the Habitica API. They are performed by making HTTP requests to the API's endpoints and asserting on the data that is returned.
If the javascript looks weird to you, that's because it's written in [ES2015](http://www.ecma-international.org/ecma-262/6.0/) and transpiled by [Babel](https://babeljs.io/docs/learn-es2015/).
## How to run the tests
First, install gulp.
```bash
$ npm install -g gulp
```
To run the api tests, make sure the mongo db is up and running and then type this on the command line:
```bash
$ gulp test:api-v2
```
It may take a little while to get going, since it requires the web server to start up before the tests can run.
You can also run a watch command for the api tests. This will allow the tests to re-run automatically when you make changes in the `/test/api/v2/`.
```bash
$ gulp test:api-v2:watch
```
One caveat. If you have a severe syntax error in your files, the tests may fail to run, but they won't alert you that they are not running. If you ever find your test hanging for a while, run this to get the stackstrace. Once you've fixed the problem, you can resume running the watch comand.
```bash
$ gulp test:api:safe
```
If you'd like to run the tests individually and inspect the output from the server, in one pane you can run:
```bash
$ gulp test:nodemon
```
And run your tests in another pane:
```bash
$ mocha path/to/file.js
# Mark a test with the `.only` attribute
$ mocha
```
## Structure
Each top level route has it's own directory. So, all the routes that begin with `/groups/` live in `/test/api/groups/`.
Each test should:
* encompase a single route
* begin with the REST parameter for the route
* display the full name of the route, swapping out `/` for `_`
* end with test.js
So, for the `POST` route `/groups/:id/leave`, it would be
```bash
POST-groups_id_leave.test.js
```
## Promises
To mitigate [callback hell](http://callbackhell.com/) :imp:, we've written a helper method to generate a user object that can make http requests that [return promises](https://babeljs.io/docs/learn-es2015/#promises). This makes it very easy to chain together commands. All you need to do to make a subsequent request is return another promise and then call `.then((result) => {})` on the surrounding block, like so:
```js
it('does something', () => {
let user;
return generateUser().then((_user) => { // We return the initial promise so this test can be run asyncronously
user = _user;
return user.post('/groups', {
type: 'party',
});
}).then((party) => { // the result of the promise above is the argument of the function
return user.put(`/groups/${party._id}`, {
name: 'My party',
});
}).then((result) => {
return user.get('/groups/party');
}).then((party) => {
expect(party.name).to.eql('My party');
});
});
```
If the test is simple, you can use the [chai-as-promised](http://chaijs.com/plugins/chai-as-promised) `return expect(somePromise).to.eventually` syntax to make your assertion.
```js
it('makes the party creator the leader automatically', () => {
return expect(user.post('/groups', {
type: 'party',
})).to.eventually.have.deep.property('leader._id', user._id);
});
```
If the test is checking that the request returns an error, use the `.eventually.be.rejected.and.eql` syntax.
```js
it('returns an error', () => {
return expect(user.get('/groups/id-of-a-party-that-user-does-not-belong-to'))
.to.eventually.be.rejected.and.eql({
code: 404,
text: t('messageGroupNotFound'),
});
});
```
## Questions?
Ask in the [Aspiring Coder's Guild](https://habitica.com/#/options/groups/guilds/68d4a01e-db97-4786-8ee3-05d612c5af6f)!
+4
View File
@@ -0,0 +1,4 @@
# How to run tests:
1. `npm test` is equivalent to `gulp test:api-v3` which will run, in order, `gulp lint`, `gulp test:api-v3:unit` and `gulp test:api-v3:integration`. If one of these fails, the whole `npm test` command blocks and fails. Each of these commands can also be run as a standalone command.
2. To run the server and the integrations tests in two different terminals (to better inspect the output in the server) run `npm start` in one and `npm test:api-v3:integration:separate-server` in the other
@@ -48,10 +48,8 @@ describe('GET /challenges/:challengeId', () => {
});
expect(chal.group).to.eql({
_id: group._id,
categories: [],
id: group.id,
name: group.name,
summary: group.name,
type: group.type,
privacy: group.privacy,
leader: groupLeader.id,
@@ -102,10 +100,8 @@ describe('GET /challenges/:challengeId', () => {
});
expect(chal.group).to.eql({
_id: group._id,
categories: [],
id: group.id,
name: group.name,
summary: group.name,
type: group.type,
privacy: group.privacy,
leader: groupLeader.id,
@@ -157,9 +153,7 @@ describe('GET /challenges/:challengeId', () => {
expect(chal.group).to.eql({
_id: group._id,
id: group.id,
categories: [],
name: group.name,
summary: group.name,
type: group.type,
privacy: group.privacy,
leader: groupLeader.id,
@@ -41,12 +41,10 @@ describe('GET challenges/user', () => {
});
expect(foundChallenge.group).to.eql({
_id: publicGuild._id,
categories: [],
id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
summary: publicGuild.name,
leader: publicGuild.leader._id,
});
});
@@ -63,12 +61,10 @@ describe('GET challenges/user', () => {
});
expect(foundChallenge1.group).to.eql({
_id: publicGuild._id,
categories: [],
id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
summary: publicGuild.name,
leader: publicGuild.leader._id,
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
@@ -80,12 +76,10 @@ describe('GET challenges/user', () => {
});
expect(foundChallenge2.group).to.eql({
_id: publicGuild._id,
categories: [],
id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
summary: publicGuild.name,
leader: publicGuild.leader._id,
});
});
@@ -102,12 +96,10 @@ describe('GET challenges/user', () => {
});
expect(foundChallenge1.group).to.eql({
_id: publicGuild._id,
categories: [],
id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
summary: publicGuild.name,
leader: publicGuild.leader._id,
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
@@ -119,26 +111,14 @@ describe('GET challenges/user', () => {
});
expect(foundChallenge2.group).to.eql({
_id: publicGuild._id,
categories: [],
id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
summary: publicGuild.name,
leader: publicGuild.leader._id,
});
});
it('should return not return challenges in user groups if we send member true param', async () => {
let challenges = await member.get(`/challenges/user?member=${true}`);
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.not.exist;
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.not.exist;
});
it('should return newest challenges first', async () => {
let challenges = await user.get('/challenges/user');
@@ -157,7 +137,6 @@ describe('GET challenges/user', () => {
let { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestPrivateGuild',
summary: 'summary for TestPrivateGuild',
type: 'guild',
privacy: 'private',
},
@@ -179,7 +158,6 @@ describe('GET challenges/user', () => {
let { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestGuild',
summary: 'summary for TestGuild',
type: 'guild',
privacy: 'public',
},
@@ -304,15 +304,5 @@ describe('POST /challenges', () => {
await expect(groupLeader.sync()).to.eventually.have.property('challenges').to.include(challenge._id);
});
it('awards achievement if this is creator\'s first challenge', async () => {
await groupLeader.post('/challenges', {
group: group._id,
name: 'Test Challenge',
shortName: 'TC Label',
});
groupLeader = await groupLeader.sync();
expect(groupLeader.achievements.joinedChallenge).to.be.true;
});
});
});
@@ -123,12 +123,5 @@ describe('POST /challenges/:challengeId/join', () => {
await expect(authorizedUser.get('/tags')).to.eventually.have.length(userTagsLength + 1);
});
it('awards achievement if this is user\'s first challenge', async () => {
await authorizedUser.post(`/challenges/${challenge._id}/join`);
await authorizedUser.sync();
expect(authorizedUser.achievements.joinedChallenge).to.be.true;
});
});
});
@@ -32,20 +32,18 @@ describe('POST /challenges/:challengeId/leave', () => {
let group;
let challenge;
let notInChallengeUser;
let notInGroupLeavingUser;
let leavingUser;
let taskText;
beforeEach(async () => {
let populatedGroup = await createAndPopulateGroup({
members: 3,
members: 2,
});
groupLeader = populatedGroup.groupLeader;
group = populatedGroup.group;
leavingUser = populatedGroup.members[0];
notInChallengeUser = populatedGroup.members[1];
notInGroupLeavingUser = populatedGroup.members[2];
challenge = await generateChallenge(groupLeader, group);
@@ -57,16 +55,17 @@ 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('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 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('returns an error when user isn\'t a member of the challenge', async () => {
@@ -118,6 +117,7 @@ describe('POST /challenges/:challengeId/leave', () => {
});
expect(testTask).to.not.be.undefined;
expect(testTask.challenge).to.eql({});
});
});
});
@@ -100,8 +100,8 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
await sleep(0.5);
await expect(winningUser.sync()).to.eventually.have.deep.property('achievements.challenges').to.include(challenge.name);
expect(winningUser.notifications.length).to.equal(2); // 2 because winningUser just joined the challenge, which now awards an achievement
expect(winningUser.notifications[1].type).to.equal('WON_CHALLENGE');
expect(winningUser.notifications.length).to.equal(1);
expect(winningUser.notifications[0].type).to.equal('WON_CHALLENGE');
});
it('gives winner gems as reward', async () => {
@@ -114,26 +114,6 @@ 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;
@@ -84,10 +84,6 @@ describe('POST /chat/:chatId/flag', () => {
type: 'party',
privacy: 'private',
});
await user.post(`/groups/${privateGroup._id}/invite`, {
uuids: [anotherUser._id],
});
await anotherUser.post(`/groups/${privateGroup._id}/join`);
let { message } = await user.post(`/groups/${privateGroup._id}/chat`, {message: TEST_MESSAGE});
let flagResult = await admin.post(`/groups/${privateGroup._id}/chat/${message.id}/flag`);
@@ -95,7 +91,7 @@ describe('POST /chat/:chatId/flag', () => {
expect(flagResult.flags[admin._id]).to.equal(true);
expect(flagResult.flagCount).to.equal(5);
let groupWithFlags = await anotherUser.get(`/groups/${privateGroup._id}`);
let groupWithFlags = await user.get(`/groups/${privateGroup._id}`);
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck).to.not.exist;
@@ -129,20 +125,4 @@ describe('POST /chat/:chatId/flag', () => {
message: t('messageGroupChatFlagAlreadyReported'),
});
});
it('shows a hidden message to the original poster', async () => {
let { message } = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
await admin.post(`/groups/${group._id}/chat/${message.id}/flag`);
let groupWithFlags = await user.get(`/groups/${group._id}`);
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck).to.exist;
let auGroupWithFlags = await anotherUser.get(`/groups/${group._id}`);
let auMessageToCheck = find(auGroupWithFlags.chat, {id: message.id});
expect(auMessageToCheck).to.not.exist;
});
});
+6 -263
View File
@@ -4,28 +4,11 @@ import {
sleep,
server,
} from '../../../../helpers/api-v3-integration.helper';
import {
SPAM_MESSAGE_LIMIT,
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
TAVERN_ID,
} from '../../../../../website/server/models/group';
import { v4 as generateUUID } from 'uuid';
import { getMatchesByWordArray, removePunctuationFromString } from '../../../../../website/server/libs/stringUtils';
import bannedWords from '../../../../../website/server/libs/bannedWords';
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 user, groupWithChat, userWithChatRevoked, member;
let testMessage = 'Test Message';
let testBannedWordMessage = 'TEST_PLACEHOLDER_SWEAR_WORD_HERE';
let testSlurMessage = 'message with TEST_PLACEHOLDER_SLUR_WORD_HERE';
let bannedWordErrorMessage = t('bannedWordUsed').split('.');
bannedWordErrorMessage[0] += ` (${removePunctuationFromString(testBannedWordMessage.toLowerCase())})`;
bannedWordErrorMessage = bannedWordErrorMessage.join('.');
before(async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({
@@ -36,10 +19,11 @@ describe('POST /chat', () => {
},
members: 2,
});
user = groupLeader;
groupWithChat = group;
userWithChatRevoked = await members[0].update({'flags.chatRevoked': true});
member = members[0];
additionalMember = members[1];
});
it('Returns an error when no message is provided', async () => {
@@ -78,225 +62,10 @@ describe('POST /chat', () => {
});
it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => {
let userWithChatRevoked = await member.update({'flags.chatRevoked': true});
await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('chatPrivilegesRevoked'),
});
});
context('banned word', () => {
it('returns an error when chat message contains a banned word in tavern', async () => {
await expect(user.post('/groups/habitrpg/chat', { message: testBannedWordMessage}))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: bannedWordErrorMessage,
});
});
it('errors when word is part of a phrase', async () => {
let wordInPhrase = `phrase ${testBannedWordMessage} end`;
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase}))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: bannedWordErrorMessage,
});
});
it('errors when word is surrounded by non alphabet characters', async () => {
let wordInPhrase = `_!${testBannedWordMessage}@_`;
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase}))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: bannedWordErrorMessage,
});
});
it('checks error message has the banned words used', async () => {
let randIndex = Math.floor(Math.random() * (bannedWords.length + 1));
let testBannedWords = bannedWords.slice(randIndex, randIndex + 2).map((w) => w.replace(/\\/g, ''));
let chatMessage = `Mixing ${testBannedWords[0]} and ${testBannedWords[1]} is bad for you.`;
await expect(user.post('/groups/habitrpg/chat', { message: chatMessage}))
.to.eventually.be.rejected
.and.have.property('message')
.that.includes(testBannedWords.join(', '));
});
it('check all banned words are matched', async () => {
let message = bannedWords.join(',').replace(/\\/g, '');
let matches = getMatchesByWordArray(message, bannedWords);
expect(matches.length).to.equal(bannedWords.length);
});
it('does not error when bad word is suffix of a word', async () => {
let wordAsSuffix = `prefix${testBannedWordMessage}`;
let message = await user.post('/groups/habitrpg/chat', { message: wordAsSuffix});
expect(message.message.id).to.exist;
});
it('does not error when bad word is prefix of a word', async () => {
let wordAsPrefix = `${testBannedWordMessage}suffix`;
let message = await user.post('/groups/habitrpg/chat', { message: wordAsPrefix});
expect(message.message.id).to.exist;
});
it('does not error when sending a chat message containing a banned word to a party', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {
name: 'Party',
type: 'party',
privacy: 'private',
},
members: 1,
});
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
expect(message.message.id).to.exist;
});
it('does not error when sending a chat message containing a banned word to a public guild', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {
name: 'public guild',
type: 'guild',
privacy: 'public',
},
members: 1,
});
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
expect(message.message.id).to.exist;
});
it('does not error when sending a chat message containing a banned word to a private guild', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {
name: 'private guild',
type: 'guild',
privacy: 'private',
},
members: 1,
});
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
expect(message.message.id).to.exist;
});
});
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}/groups/guild/${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});
code: 404,
error: 'NotFound',
message: 'Your chat privileges have been revoked.',
});
});
@@ -404,30 +173,4 @@ describe('POST /chat', () => {
expect(message.message.id).to.exist;
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
});
context('Spam prevention', () => {
it('Returns an error when the user has been posting too many messages', async () => {
// Post as many messages are needed to reach the spam limit
for (let i = 0; i < SPAM_MESSAGE_LIMIT; i++) {
let result = await additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop
expect(result.message.id).to.exist;
}
await expect(additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('messageGroupChatSpam'),
});
});
it('contributor should not receive spam alert', async () => {
let userSocialite = await member.update({'contributor.level': SPAM_MIN_EXEMPT_CONTRIB_LEVEL, 'flags.chatRevoked': false});
// Post 1 more message than the spam limit to ensure they do not reach the limit
for (let i = 0; i < SPAM_MESSAGE_LIMIT + 1; i++) {
let result = await userSocialite.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop
expect(result.message.id).to.exist;
}
});
});
});
@@ -1,32 +0,0 @@
import {
generateUser,
generateGroup,
} from '../../../../helpers/api-v3-integration.helper';
describe('GET /group-plans', () => {
let user;
let groupPlan;
before(async () => {
user = await generateUser({balance: 4});
groupPlan = await generateGroup(user,
{
name: 'public guild - is member',
type: 'guild',
privacy: 'public',
},
{
purchased: {
plan: {
customerId: 'existings',
},
},
});
});
it('returns group plans for the user', async () => {
let groupPlans = await user.get('/group-plans');
expect(groupPlans[0]._id).to.eql(groupPlan._id);
});
});
@@ -7,7 +7,6 @@ import {
import {
TAVERN_ID,
} from '../../../../../website/server/models/group';
import apiMessages from '../../../../../website/server/libs/apiMessages';
describe('GET /groups', () => {
let user;
@@ -15,13 +14,6 @@ describe('GET /groups', () => {
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1;
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();
@@ -37,18 +29,16 @@ describe('GET /groups', () => {
await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id]});
await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`);
publicGuildNotMember = await generateGroup(leader, {
await generateGroup(leader, {
name: 'public guild - is not member',
type: 'guild',
privacy: 'public',
categories,
});
privateGuildUserIsMemberOf = await generateGroup(leader, {
let 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`);
@@ -108,104 +98,6 @@ 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'))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'Invalid request parameters.',
});
});
it('req.query.paginate can only be true when req.query.type includes publicGuilds', async () => {
await expect(user.get('/groups?paginate=true&type=notPublicGuilds'))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: apiMessages('guildsOnlyPaginate'),
});
});
it('req.query.page can\'t be negative', async () => {
await expect(user.get('/groups?paginate=true&page=-1&type=publicGuilds'))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'Invalid request parameters.',
});
});
it('returns 30 guilds per page ordered by number of members', async () => {
await user.update({balance: 9000});
let groups = await Promise.all(_.times(60, (i) => {
return generateGroup(user, {
name: `public guild ${i} - is member`,
type: 'guild',
privacy: 'public',
});
}));
// update group number 32 and not the first to make sure sorting works
await groups[32].update({name: 'guild with most members', memberCount: 199});
await groups[33].update({name: 'guild with less members', memberCount: -100});
let page0 = await expect(user.get('/groups?type=publicGuilds&paginate=true'))
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
expect(page0[0].name).to.equal('guild with most members');
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 + 3); // 1 created now, 3 by other tests
expect(page2[3].name).to.equal('guild with less members');
});
});
it('returns all the user\'s guilds when guilds passed in as query', async () => {
await expect(user.get('/groups?type=guilds'))
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER + NUMBER_OF_USERS_PRIVATE_GUILDS);
@@ -63,17 +63,15 @@ describe('GET /groups/:groupId/invites', () => {
});
it('returns only first 30 invites', async () => {
let leader = await generateUser({balance: 4});
let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()});
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
let invitesToGenerate = [];
for (let i = 0; i < 31; i++) {
invitesToGenerate.push(generateUser());
}
let generatedInvites = await Promise.all(invitesToGenerate);
await leader.post(`/groups/${group._id}/invite`, {uuids: generatedInvites.map(invite => invite._id)});
await user.post(`/groups/${group._id}/invite`, {uuids: generatedInvites.map(invite => invite._id)});
let res = await leader.get(`/groups/${group._id}/invites`);
let res = await user.get('/groups/party/invites');
expect(res.length).to.equal(30);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
@@ -66,25 +66,11 @@ describe('GET /groups/:groupId/members', () => {
expect(res[0].profile).to.have.all.keys(['name']);
});
it('req.query.includeAllPublicFields === true works with guilds', async () => {
it('req.query.includeAllPublicFields === true only works with parties', async () => {
let group = await generateGroup(user, {type: 'guild', name: generateUUID()});
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;
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']);
});
it('populates all public fields if req.query.includeAllPublicFields === true and it is a party', async () => {
@@ -98,7 +84,7 @@ describe('GET /groups/:groupId/members', () => {
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',
'chair', 'costume', 'sleep', 'background',
].sort());
expect(memberRes.stats.maxMP).to.exist;
@@ -1,93 +0,0 @@
import {
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { find } from 'lodash';
describe('POST /group/:groupId/remove-manager', () => {
let leader, nonLeader, groupToUpdate;
let groupName = 'Test Public Guild';
let groupType = 'guild';
let nonManager;
function findAssignedTask (memberTask) {
return memberTask.group.id === groupToUpdate._id;
}
beforeEach(async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({
groupDetails: {
name: groupName,
type: groupType,
privacy: 'public',
},
members: 1,
});
groupToUpdate = group;
leader = groupLeader;
nonLeader = members[0];
nonManager = members[0];
});
it('returns an error when a non group leader tries to add member', async () => {
await expect(nonLeader.post(`/groups/${groupToUpdate._id}/remove-manager`, {
managerId: nonLeader._id,
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('messageGroupOnlyLeaderCanUpdate'),
});
});
it('returns an error when manager does not exist', async () => {
await expect(leader.post(`/groups/${groupToUpdate._id}/remove-manager`, {
managerId: nonManager._id,
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('userIsNotManager'),
});
});
it('allows a leader to remove managers', async () => {
await leader.post(`/groups/${groupToUpdate._id}/add-manager`, {
managerId: nonLeader._id,
});
let updatedGroup = await leader.post(`/groups/${groupToUpdate._id}/remove-manager`, {
managerId: nonLeader._id,
});
expect(updatedGroup.managers[nonLeader._id]).to.not.exist;
});
it('removes group approval notifications from a manager that is removed', async () => {
await leader.post(`/groups/${groupToUpdate._id}/add-manager`, {
managerId: nonLeader._id,
});
let task = await leader.post(`/tasks/group/${groupToUpdate._id}`, {
text: 'test todo',
type: 'todo',
requiresApproval: true,
});
await nonLeader.post(`/tasks/${task._id}/assign/${leader._id}`);
let memberTasks = await leader.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(leader.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
let updatedGroup = await leader.post(`/groups/${groupToUpdate._id}/remove-manager`, {
managerId: nonLeader._id,
});
await nonLeader.sync();
expect(nonLeader.notifications.length).to.equal(0);
expect(updatedGroup.managers[nonLeader._id]).to.not.exist;
});
});
@@ -74,18 +74,6 @@ describe('POST /group', () => {
expect(updatedUser.guilds).to.include(guild._id);
});
it('awards the Joined Guild achievement', async () => {
await user.post('/groups', {
name: 'some guild',
type: 'guild',
privacy: 'public',
});
let updatedUser = await user.get('/user');
expect(updatedUser.achievements.joinedGuild).to.eql(true);
});
context('public guild', () => {
it('creates a group', async () => {
let groupName = 'Test Public Guild';

Some files were not shown because too many files have changed in this diff Show More