Compare commits

..

2 Commits

Author SHA1 Message Date
Sabe Jones 0992b656fa 3.96.0 2017-06-16 20:41:58 +00:00
Sabe Jones cf49f4d159 feat(migration): update achievements 2017-06-16 19:55:13 +00:00
10328 changed files with 161422 additions and 161049 deletions
-1
View File
@@ -1,7 +1,6 @@
{
"presets": ["es2015"],
"plugins": [
"transform-object-rest-spread",
["transform-async-to-module-method", {
"module": "bluebird",
"method": "coroutine"
+3
View File
@@ -0,0 +1,3 @@
{
"directory": "website/client-old/bower_components"
}
+7 -3
View File
@@ -14,13 +14,17 @@ 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"
02_ownBabel:
command: "chmod a+rw /tmp/.babel.json"
03_installGulp:
03_installBower:
command: "$NODE_HOME/bin/npm install -g bower"
04_installGulp:
command: "$NODE_HOME/bin/npm install -g gulp"
04_runGulp:
05_runBower:
command: "$NODE_HOME/lib/node_modules/bower/bin/bower --config.interactive=false --allow-root install -f"
06_runGulp:
command: "$NODE_HOME/lib/node_modules/gulp/bin/gulp.js build"
+2 -3
View File
@@ -6,9 +6,6 @@ website/transpiled-babel/
website/common/transpiled-babel/
dist/
dist-client/
apidoc_build/
content_cache/
node_modules/
# Not linted
website/client-old/
@@ -19,3 +16,5 @@ migrations/*
scripts/*
website/common/browserify.js
Gruntfile.js
gulpfile.js
gulp
+1 -1
View File
@@ -4,7 +4,7 @@
# Pull Request
[Please see these instructions for adding a pull request](http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API)
[Please see these instructions for adding a pull request](http://habitica.wikia.com/wiki/Using_Habitica_Git#Pull_Request)
# Requesting a feature
+1 -1
View File
@@ -6,7 +6,7 @@
[//]: # (For more guidelines see https://github.com/HabitRPG/habitica/issues/2760)
[//]: # (Fill out relevant information - UUID is found from the Habitia website at User Icon > Settings > API)
[//]: # (Fill out relevant information - UUID is found in Settings -> API)
### General Info
* UUID:
* Browser:
+2 -2
View File
@@ -1,4 +1,4 @@
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API for more info)
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Habitica_Git#Pull_Request for more info)
[//]: # (Put Issue # or URL here, if applicable. This will automatically close the issue if your PR is merged in)
Fixes put_issue_url_here
@@ -8,7 +8,7 @@ Fixes put_issue_url_here
[//]: # (Put User ID in here - found on the Habitica website at User Icon > Settings > API)
[//]: # (Put User ID in here - found in Settings -> API)
----
UUID:
-3
View File
@@ -2,14 +2,11 @@
website/client-old/gen
website/client-old/common
website/client-old/apidoc
website/build
website/client-old/js/habitrpg-shared.js*
website/client-old/css/habitrpg-shared.css
website/transpiled-babel/
website/common/transpiled-babel/
node_modules
content_cache
apidoc_build
*.swp
.idea*
config.json
+16 -7
View File
@@ -1,22 +1,29 @@
language: node_js
node_js:
- '6'
services:
- mongodb
cache:
directories:
- 'node_modules'
sudo: required
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
before_install:
- npm install -g npm@5
- $CXX --version
- 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 5
- if [ $REQUIRES_SERVER ]; then until nc -z localhost 27017; do echo Waiting for MongoDB; sleep 1; done; export DISPLAY=:99; fi
script:
- npm run $TEST
- if [ $COVERAGE ]; then ./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js; fi
env:
global:
- CXX=g++-4.8
- DISABLE_REQUEST_LOGGING=true
matrix:
- TEST="lint"
@@ -24,4 +31,6 @@ env:
- TEST="test:sanity"
- TEST="test:content" COVERAGE=true
- TEST="test:common" COVERAGE=true
- TEST="test:karma" COVERAGE=true
- TEST="client:unit" COVERAGE=true
- TEST="apidoc"
+18 -21
View File
@@ -1,21 +1,18 @@
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 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
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["npm", "start"]
FROM node:boron
# Install global packages
RUN npm install -g gulp grunt-cli bower mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN npm install
RUN bower install --allow-root
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["npm", "start"]
-32
View File
@@ -1,32 +0,0 @@
FROM node:boron
ENV ADMIN_EMAIL admin@habitica.com
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
ENV AMAZON_PAYMENTS_SELLER_ID AMQ3SB4SG5E91
ENV AMPLITUDE_KEY e8d4c24b3d6ef3ee73eeba715023dd43
ENV BASE_URL https://habitica.com
ENV FACEBOOK_KEY 128307497299777
ENV GA_ID UA-33510635-1
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
ENV NODE_ENV production
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
# 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 mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone --branch v4.23.2 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN npm install
RUN gulp build:prod --force
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["node", "./website/transpiled-babel/index.js"]
+142
View File
@@ -0,0 +1,142 @@
/*global module:false*/
require('babel-register');
var _ = require('lodash');
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
karma: {
unit: {
configFile: 'test/client-old/spec/karma.conf.js'
},
continuous: {
configFile: 'test/client-old/spec/karma.conf.js',
singleRun: true,
autoWatch: false
}
},
clean: {
build: ['website/build']
},
cssmin: {
dist: {
options: {
report: 'gzip'
},
files:{
"website/client-old/css/habitrpg-shared.css": [
"website/assets/sprites/dist/spritesmith*.css",
"website/assets/sprites/css/backer.css",
"website/assets/sprites/css/Mounts.css",
"website/assets/sprites/css/index.css"
]
}
}
},
stylus: {
build: {
options: {
compress: false, // AFTER
'include css': true,
paths: ['website/client-old']
},
files: {
'website/build/app.css': ['website/client-old/css/index.styl'],
'website/build/static.css': ['website/client-old/css/static.styl']
}
}
},
copy: {
build: {
files: [
{expand: true, cwd: 'website/client-old/', src: 'favicon.ico', dest: 'website/build/'},
{expand: true, cwd: 'website/client-old/', src: 'favicon_192x192.png', dest: 'website/build/'},
{expand: true, cwd: 'website/assets/sprites/dist/', src: 'spritesmith*.png', dest: 'website/build/static/sprites'},
{expand: true, cwd: 'website/assets/sprites/', src: 'backer-only/*.gif', dest: 'website/build/'},
{expand: true, cwd: 'website/assets/sprites/', src: 'npc_ian.gif', dest: 'website/build/'},
{expand: true, cwd: 'website/assets/sprites/', src: 'quest_*.gif', dest: 'website/build/'},
{expand: true, cwd: 'website/client-old/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'website/build/'}
]
}
},
// UPDATE IT WHEN YOU ADD SOME FILES NOT ALREADY MATCHED!
hashres: {
build: {
options: {
fileNameFormat: '${name}-${hash}.${ext}'
},
src: [
'website/build/*.js',
'website/build/*.css',
'website/build/favicon.ico',
'website/build/favicon_192x192.png',
'website/build/*.png',
'website/build/static/sprites/*.png',
'website/build/*.gif',
'website/build/bower_components/bootstrap/dist/fonts/*'
],
dest: 'website/build/*.css'
}
}
});
//Load build files from client-old/manifest.json
grunt.registerTask('loadManifestFiles', 'Load all build files from client-old/manifest.json', function(){
var files = grunt.file.readJSON('./website/client-old/manifest.json');
var uglify = {};
var cssmin = {};
_.each(files, function(val, key){
var js = uglify['website/build/' + key + '.js'] = [];
_.each(files[key].js, function(val){
var path = "./";
if( val.indexOf('common/') == -1)
path = './website/client-old/';
js.push(path + val);
});
var css = cssmin['website/build/' + key + '.css'] = [];
_.each(files[key].css, function(val){
var path = "./";
if( val.indexOf('common/') == -1) {
path = (val == 'app.css' || val == 'static.css') ? './website/build/' : './website/client-old/';
}
css.push(path + val)
});
});
grunt.config.set('uglify.build.files', uglify);
grunt.config.set('uglify.build.options', {compress: false});
grunt.config.set('cssmin.build.files', cssmin);
// Rewrite urls to relative path
grunt.config.set('cssmin.build.options', {'target': 'website/client-old/css/whatever-css.css'});
});
// Register tasks.
grunt.registerTask('build:prod', ['loadManifestFiles', 'clean:build', 'uglify', 'stylus', 'cssmin', 'copy:build', 'hashres']);
grunt.registerTask('build:dev', ['cssmin', 'stylus']);
grunt.registerTask('build:test', ['build:dev']);
// Load tasks
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-stylus');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-hashres');
if (process.env.NODE_ENV !== 'production') grunt.loadNpmTasks('grunt-karma');
};
-2
View File
@@ -16,7 +16,5 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.hostname = "habitrpg"
config.vm.network "forwarded_port", guest: 3000, host: 3000, auto_correct: true
config.vm.usable_port_range = (3000..3050)
config.vm.network "forwarded_port", guest: 8080, host: 8080, auto_correct: true
config.vm.usable_port_range = (8080..8130)
config.vm.provision :shell, :path => "vagrant_scripts/vagrant.sh"
end
+56
View File
@@ -0,0 +1,56 @@
{
"name": "HabitRPG",
"version": "0.1.1",
"homepage": "https://github.com/lefnire/habitrpg",
"authors": [
"Tyler Renelle <tylerrenelle@gmail.com>"
],
"private": true,
"ignore": [
"**/.*",
"node_modules",
"website/client-old/bower_components",
"test",
"tests"
],
"dependencies": {
"Angular-At-Directive": "snicker/Angular-At-Directive#c27bae207aa06d1e",
"angular": "1.3.9",
"angular-bootstrap": "0.13.0",
"angular-filter": "0.5.1",
"angular-loading-bar": "0.6.0",
"angular-resource": "1.3.9",
"angular-sanitize": "1.3.9",
"angular-ui": "0.4.0",
"angular-ui-router": "0.2.13",
"angular-ui-select2": "angular-ui/ui-select2#afa6589a54cb72815f",
"angular-ui-utils": "0.1.0",
"bootstrap": "3.1.0",
"bootstrap-growl": "ifightcrime/bootstrap-growl#162daa41cd1155f",
"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",
"jquery": "2.1.0",
"jquery-colorbox": "1.4.36",
"jquery-ui": "1.10.3",
"jquery.cookie": "1.4.0",
"js-emoji": "snicker/js-emoji#f25d8a303f",
"ngInfiniteScroll": "1.1.0",
"pnotify": "1.3.1",
"sticky": "1.0.3",
"swagger-ui": "wordnik/swagger-ui#v2.0.24",
"smart-app-banner": "78ef9c0679723b25be1a0ae04f7b4aef7cbced4f",
"habitica-markdown": "1.2.2",
"pusher-js-auth": "^2.0.0",
"pusher-websocket-iso": "pusher#^3.2.0",
"taggle": "^1.11.1"
},
"devDependencies": {
"angular-mocks": "1.3.9"
},
"resolutions": {
"angular": "1.3.9",
"jquery": ">=1.9.0"
}
}
+1 -5
View File
@@ -22,8 +22,6 @@
"CRON_SEMI_SAFE_MODE":"false",
"MAINTENANCE_MODE": "false",
"SESSION_SECRET":"YOUR SECRET HERE",
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
"SESSION_SECRET_IV": "12345678912345678912345678912345",
"ADMIN_EMAIL": "you@example.com",
"SMTP_USER":"user@example.com",
"SMTP_PASS":"password",
@@ -68,12 +66,10 @@
},
"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_CLIENT_TOKEN": "token",
"LOGGLY_ACCOUNT": "account",
"PUSH_CONFIGS": {
"GCM_SERVER_API_KEY": "",
+3 -10
View File
@@ -1,10 +1,3 @@
version: "3"
services:
client:
volumes:
- '.:/usr/src/habitrpg'
server:
volumes:
- '.:/usr/src/habitrpg'
web:
volumes:
- '.:/habitrpg'
+12 -35
View File
@@ -1,36 +1,13 @@
version: "3"
services:
web:
build: .
ports:
- "3000:3000"
links:
- mongo
environment:
- NODE_DB_URI=mongodb://mongo/habitrpg
client:
build: .
networks:
- habitica
environment:
- BASE_URL=http://server:3000
ports:
- "8080:8080"
command: ["npm", "run", "client:dev"]
depends_on:
- server
server:
build: .
ports:
- "3000:3000"
networks:
- habitica
environment:
- NODE_DB_URI=mongodb://mongo/habitrpg
depends_on:
- mongo
mongo:
image: mongo
ports:
- "27017:27017"
networks:
- habitica
networks:
habitica:
driver: bridge
mongo:
image: mongo
ports:
- "27017:27017"
+10
View File
@@ -0,0 +1,10 @@
{
"root": true,
"env": {
"node": true,
},
"extends": [
"habitrpg/server",
"habitrpg/babel"
],
}
+2 -2
View File
@@ -2,7 +2,7 @@ import gulp from 'gulp';
import clean from 'rimraf';
import apidoc from 'apidoc';
const APIDOC_DEST_PATH = './apidoc_build';
const APIDOC_DEST_PATH = './website/build/apidoc';
const APIDOC_SRC_PATH = './website/server';
gulp.task('apidoc:clean', (done) => {
clean(APIDOC_DEST_PATH, done);
@@ -22,5 +22,5 @@ gulp.task('apidoc', ['apidoc:clean'], (done) => {
});
gulp.task('apidoc:watch', ['apidoc'], () => {
return gulp.watch(`${APIDOC_SRC_PATH}/**/*.js`, ['apidoc']);
return gulp.watch(APIDOC_SRC_PATH + '/**/*.js', ['apidoc']);
});
+31
View File
@@ -0,0 +1,31 @@
import gulp from 'gulp';
import browserify from 'browserify';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import uglify from 'gulp-uglify';
import sourcemaps from 'gulp-sourcemaps';
import babel from 'babelify';
gulp.task('browserify', function () {
let bundler = browserify({
entries: './website/common/browserify.js',
debug: true,
transform: [[babel, { compact: false }]],
});
return bundler.bundle()
.pipe(source('habitrpg-shared.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(uglify())
.on('error', function (err) {
console.error(err);
this.emit('end');
})
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./website/client-old/js/'));
});
gulp.task('browserify:watch', () => {
gulp.watch('./website/common/script/**/*.js', ['browserify']);
});
+36
View File
@@ -0,0 +1,36 @@
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,
);
});
+18 -13
View File
@@ -1,10 +1,13 @@
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', () => {
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
if (process.env.NODE_ENV === 'production') {
gulp.start('build:prod');
} else {
gulp.start('build:dev');
}
});
@@ -22,16 +25,18 @@ gulp.task('build:common', () => {
gulp.task('build:server', ['build:src', 'build:common']);
// Client Production Build
gulp.task('build:client', (done) => {
webpackProductionBuild((err, output) => {
if (err) return done(err);
console.log(output); // eslint-disable-line no-console
});
gulp.task('build:dev', ['browserify', 'prepare:staticNewStuff'], (done) => {
gulp.start('grunt-build:dev', done);
});
gulp.task('build:prod', [
'build:server',
'build:client',
'apidoc',
]);
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'], (done) => {
runSequence(
'grunt-build:prod',
'apidoc',
done
);
});
+10 -10
View File
@@ -7,11 +7,10 @@ import gulp from 'gulp';
// Add additional properties to the repl's context
let improveRepl = (context) => {
// Let "exit" and "quit" terminate the console
['exit', 'quit'].forEach((term) => {
Object.defineProperty(context, term, { get () {
process.exit();
}});
Object.defineProperty(context, term, { get () { process.exit(); }});
});
// "clear" clears the screen
@@ -19,12 +18,12 @@ let improveRepl = (context) => {
process.stdout.write('\u001B[2J\u001B[0;0f');
}});
context.Challenge = require('../website/server/models/challenge').model; // eslint-disable-line global-require
context.Group = require('../website/server/models/group').model; // eslint-disable-line global-require
context.User = require('../website/server/models/user').model; // eslint-disable-line global-require
context.Challenge = require('../website/server/models/challenge').model;
context.Group = require('../website/server/models/group').model;
context.User = require('../website/server/models/user').model;
const isProd = nconf.get('NODE_ENV') === 'production';
const mongooseOptions = !isProd ? {} : {
var isProd = nconf.get('NODE_ENV') === 'production';
var mongooseOptions = !isProd ? {} : {
replset: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
};
@@ -32,15 +31,16 @@ let improveRepl = (context) => {
mongoose.connect(
nconf.get('NODE_DB_URI'),
mongooseOptions,
(err) => {
function (err) {
if (err) throw err;
logger.info('Connected with Mongoose');
}
)
);
};
gulp.task('console', () => {
gulp.task('console', (cb) => {
improveRepl(repl.start({
prompt: 'Habitica > ',
}).context);
+10
View File
@@ -0,0 +1,10 @@
import gulp from 'gulp';
import jade from 'jade';
import {writeFileSync} from 'fs';
gulp.task('prepare:staticNewStuff', () => {
writeFileSync(
'./website/client-old/new-stuff.html',
jade.compileFile('./website/views/shared/new-stuff.jade')()
);
});
+104 -105
View File
@@ -10,39 +10,82 @@ import {each} from 'lodash';
// https://github.com/Ensighten/grunt-spritesmith/issues/67#issuecomment-34786248
const MAX_SPRITESHEET_SIZE = 1024 * 1024 * 3;
const DIST_PATH = 'website/assets/sprites/dist/';
const IMG_DIST_PATH = 'website/client/assets/images/sprites/';
const CSS_DIST_PATH = 'website/client/assets/css/sprites/';
const IMG_DIST_PATH_NEW_CLIENT = 'website/static/sprites/';
const CSS_DIST_PATH_NEW_CLIENT = 'website/client/assets/css/sprites/';
function checkForSpecialTreatment (name) {
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
return name.match(regex) || name === 'head_0';
}
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
function calculateImgDimensions (img, addPadding) {
let dims = sizeOf(img);
gulp.task('sprites:main', () => {
let mainSrc = sync('website/assets/sprites/spritesmith/**/*.png');
return createSpritesStream('main', mainSrc);
});
let requiresSpecialTreatment = checkForSpecialTreatment(img);
if (requiresSpecialTreatment) {
let newWidth = dims.width < 90 ? 90 : dims.width;
let newHeight = dims.height < 90 ? 90 : dims.height;
dims = {
width: newWidth,
height: newHeight,
};
gulp.task('sprites:largeSprites', () => {
let largeSrc = sync('website/assets/sprites/spritesmith_large/**/*.png');
return createSpritesStream('largeSprites', largeSrc);
});
gulp.task('sprites:clean', (done) => {
clean(`{${DIST_PATH}spritesmith*,${IMG_DIST_PATH_NEW_CLIENT}spritesmith*,${CSS_DIST_PATH_NEW_CLIENT}spritesmith*}`, done);
});
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
console.log('Verifiying that images do not exceed max dimensions');
let numberOfSheetsThatAreTooBig = 0;
let distSpritesheets = sync(`${DIST_PATH}*.png`);
each(distSpritesheets, (img, index) => {
let spriteSize = calculateImgDimensions(img);
if (spriteSize > MAX_SPRITESHEET_SIZE) {
numberOfSheetsThatAreTooBig++;
let name = basename(img, '.png');
console.error(`WARNING: ${name} might be too big - ${spriteSize} > ${MAX_SPRITESHEET_SIZE}`);
}
});
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
} else {
console.log('All images are within the correct dimensions');
}
});
let padding = 0;
function createSpritesStream (name, src) {
let spritesheetSliceIndicies = calculateSpritesheetsSrcIndicies(src);
let stream = mergeStream();
if (addPadding) {
padding = dims.width * 8 + dims.height * 8;
}
each(spritesheetSliceIndicies, (start, index) => {
let slicedSrc = src.slice(start, spritesheetSliceIndicies[index + 1]);
if (!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims); // eslint-disable-line no-console
let spriteData = gulp.src(slicedSrc)
.pipe(spritesmith({
imgName: `spritesmith-${name}-${index}.png`,
cssName: `spritesmith-${name}-${index}.css`,
algorithm: 'binary-tree',
padding: 1,
cssTemplate: 'website/assets/sprites/css/css.template.handlebars',
cssVarMap: cssVarMap,
}));
let totalPixelSize = dims.width * dims.height + padding;
let imgStream = spriteData.img
.pipe(imagemin())
.pipe(gulp.dest(IMG_DIST_PATH_NEW_CLIENT))
.pipe(gulp.dest(DIST_PATH));
return totalPixelSize;
let cssStream = spriteData.css
.pipe(gulp.dest(CSS_DIST_PATH_NEW_CLIENT))
.pipe(gulp.dest(DIST_PATH));
stream.add(imgStream);
stream.add(cssStream);
});
return stream;
}
function calculateSpritesheetsSrcIndicies (src) {
@@ -62,6 +105,37 @@ function calculateSpritesheetsSrcIndicies (src) {
return slices;
}
function calculateImgDimensions (img, addPadding) {
let dims = sizeOf(img);
let requiresSpecialTreatment = checkForSpecialTreatment(img);
if (requiresSpecialTreatment) {
let newWidth = dims.width < 90 ? 90 : dims.width;
let newHeight = dims.height < 90 ? 90 : dims.height;
dims = {
width: newWidth,
height: newHeight,
};
}
let padding = 0;
if (addPadding) {
padding = (dims.width * 8) + (dims.height * 8);
}
if (!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims);
let totalPixelSize = (dims.width * dims.height) + padding;
return totalPixelSize;
}
function checkForSpecialTreatment (name) {
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
return name.match(regex) || name === 'head_0';
}
function cssVarMap (sprite) {
// For hair, skins, beards, etc. we want to output a '.customize-options.WHATEVER' class, which works as a
// 60x60 image pointing at the proper part of the 90x90 sprite.
@@ -70,93 +144,18 @@ function cssVarMap (sprite) {
if (requiresSpecialTreatment) {
sprite.custom = {
px: {
offsetX: `-${ sprite.x + 25 }px`,
offsetY: `-${ sprite.y + 15 }px`,
offset_x: `-${ sprite.x + 25 }px`,
offset_y: `-${ sprite.y + 15 }px`,
width: '60px',
height: '60px',
},
};
}
if (sprite.name.indexOf('shirt') !== -1)
sprite.custom.px.offsetY = `-${ sprite.y + 35 }px`; // even more for shirts
if (sprite.name.indexOf('hair_base') !== -1) {
let styleArray = sprite.name.split('_').slice(2, 3);
if (~sprite.name.indexOf('shirt'))
sprite.custom.px.offset_y = `-${ sprite.y + 30 }px`; // even more for shirts
if (~sprite.name.indexOf('hair_base')) {
let styleArray = sprite.name.split('_').slice(2,3);
if (Number(styleArray[0]) > 14)
sprite.custom.px.offsetY = `-${ sprite.y }px`; // don't crop updos
sprite.custom.px.offset_y = `-${ sprite.y }px`; // don't crop updos
}
}
function createSpritesStream (name, src) {
let spritesheetSliceIndicies = calculateSpritesheetsSrcIndicies(src);
let stream = mergeStream();
each(spritesheetSliceIndicies, (start, index) => {
let slicedSrc = src.slice(start, spritesheetSliceIndicies[index + 1]);
let spriteData = gulp.src(slicedSrc)
.pipe(spritesmith({
imgName: `spritesmith-${name}-${index}.png`,
cssName: `spritesmith-${name}-${index}.css`,
algorithm: 'binary-tree',
padding: 1,
cssTemplate: 'website/raw_sprites/css/css.template.handlebars',
cssVarMap,
}));
let imgStream = spriteData.img
.pipe(imagemin())
.pipe(gulp.dest(IMG_DIST_PATH));
let cssStream = spriteData.css
.pipe(gulp.dest(CSS_DIST_PATH));
stream.add(imgStream);
stream.add(cssStream);
});
return stream;
}
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
gulp.task('sprites:main', () => {
let mainSrc = sync('website/raw_sprites/spritesmith/**/*.png');
return createSpritesStream('main', mainSrc);
});
gulp.task('sprites:largeSprites', () => {
let largeSrc = sync('website/raw_sprites/spritesmith_large/**/*.png');
return createSpritesStream('largeSprites', largeSrc);
});
gulp.task('sprites:clean', (done) => {
clean(`${IMG_DIST_PATH}spritesmith*,${CSS_DIST_PATH}spritesmith*}`, done);
});
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
console.log('Verifiying that images do not exceed max dimensions'); // eslint-disable-line no-console
let numberOfSheetsThatAreTooBig = 0;
let distSpritesheets = sync(`${IMG_DIST_PATH}*.png`);
each(distSpritesheets, (img) => {
let spriteSize = calculateImgDimensions(img);
if (spriteSize > MAX_SPRITESHEET_SIZE) {
numberOfSheetsThatAreTooBig++;
let name = basename(img, '.png');
console.error(`WARNING: ${name} might be too big - ${spriteSize} > ${MAX_SPRITESHEET_SIZE}`); // eslint-disable-line no-console
}
});
if (numberOfSheetsThatAreTooBig > 0) {
// https://github.com/HabitRPG/habitica/pull/6683#issuecomment-185462180
console.error( // eslint-disable-line no-console
`${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.`);
} else {
console.log('All images are within the correct dimensions'); // eslint-disable-line no-console
}
});
+2
View File
@@ -3,6 +3,8 @@ import nodemon from 'gulp-nodemon';
let pkg = require('../package.json');
gulp.task('run:dev', ['nodemon', 'build:dev:watch']);
gulp.task('nodemon', () => {
nodemon({
script: pkg.main,
+136 -21
View File
@@ -1,12 +1,21 @@
import {
pipe,
awaitPort,
kill,
runMochaTests,
} from './taskHelper';
import { server as karma } from 'karma';
import mongoose from 'mongoose';
import { exec } from 'child_process';
import psTree from 'ps-tree';
import gulp from 'gulp';
import Bluebird from 'bluebird';
import runSequence from 'run-sequence';
import os from 'os';
import nconf from 'nconf';
import fs from 'fs';
const i18n = require('../website/server/libs/i18n');
// TODO rewrite
@@ -15,23 +24,25 @@ let server;
const TEST_DB_URI = nconf.get('TEST_DB_URI');
const API_V3_TEST_COMMAND = 'npm run test:api-v3';
const SANITY_TEST_COMMAND = 'npm run test:sanity';
const COMMON_TEST_COMMAND = 'npm run test:common';
const CONTENT_TEST_COMMAND = 'npm run test:content';
const CONTENT_OPTIONS = {maxBuffer: 1024 * 500};
const KARMA_TEST_COMMAND = 'npm run test:karma';
/* Helper methods for reporting test summary */
let testResults = [];
let testCount = (stdout, regexp) => {
let match = stdout.match(regexp);
return parseInt(match && match[1] || 0, 10);
return parseInt(match && match[1] || 0);
};
let testBin = (string, additionalEnvVariables = '') => {
if (os.platform() === 'win32') {
if (additionalEnvVariables !== '') {
if (additionalEnvVariables != '') {
additionalEnvVariables = additionalEnvVariables.split(' ').join('&&set ');
additionalEnvVariables = `set ${additionalEnvVariables}&&`;
additionalEnvVariables = 'set ' + additionalEnvVariables + '&&';
}
return `set NODE_ENV=test&&${additionalEnvVariables}${string}`;
} else {
@@ -39,9 +50,9 @@ let testBin = (string, additionalEnvVariables = '') => {
}
};
gulp.task('test:nodemon', () => {
process.env.PORT = TEST_SERVER_PORT; // eslint-disable-line no-process-env
process.env.NODE_DB_URI = TEST_DB_URI; // eslint-disable-line no-process-env
gulp.task('test:nodemon', (done) => {
process.env.PORT = TEST_SERVER_PORT;
process.env.NODE_DB_URI = TEST_DB_URI;
runSequence('nodemon');
});
@@ -58,27 +69,37 @@ gulp.task('test:prepare:mongo', (cb) => {
gulp.task('test:prepare:server', ['test:prepare:mongo'], () => {
if (!server) {
server = exec(testBin('node ./website/server/index.js', `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => {
if (error) {
throw new Error(`Problem with the server: ${error}`);
}
if (stderr) {
console.error(stderr); // eslint-disable-line no-console
}
if (error) { throw `Problem with the server: ${error}`; }
if (stderr) { console.error(stderr); }
});
}
});
gulp.task('test:prepare:build', ['build']);
gulp.task('test:prepare:translations', (cb) => {
fs.writeFile(
'test/client-old/spec/mocks/translations.js',
`if(!window.env) window.env = {};
window.env.translations = ${JSON.stringify(i18n.translations['en'])};`, cb);
});
gulp.task('test:prepare:build', ['build', 'test:prepare:translations']);
// exec(testBin('grunt build:test'), cb);
gulp.task('test:prepare:webdriver', (cb) => {
exec('npm run test:prepare:webdriver', cb);
});
gulp.task('test:prepare', [
'test:prepare:build',
'test:prepare:mongo',
'test:prepare:webdriver',
]);
gulp.task('test:sanity', (cb) => {
let runner = exec(
testBin(SANITY_TEST_COMMAND),
(err) => {
(err, stdout, stderr) => {
if (err) {
process.exit(1);
}
@@ -91,7 +112,7 @@ gulp.task('test:sanity', (cb) => {
gulp.task('test:common', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(COMMON_TEST_COMMAND),
(err) => {
(err, stdout, stderr) => {
if (err) {
process.exit(1);
}
@@ -112,7 +133,7 @@ gulp.task('test:common:watch', ['test:common:clean'], () => {
gulp.task('test:common:safe', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(COMMON_TEST_COMMAND),
(err, stdout) => { // eslint-disable-line handle-callback-err
(err, stdout, stderr) => {
testResults.push({
suite: 'Common Specs\t',
pass: testCount(stdout, /(\d+) passing/),
@@ -129,7 +150,7 @@ gulp.task('test:content', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(CONTENT_TEST_COMMAND),
CONTENT_OPTIONS,
(err) => {
(err, stdout, stderr) => {
if (err) {
process.exit(1);
}
@@ -151,7 +172,7 @@ gulp.task('test:content:safe', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(CONTENT_TEST_COMMAND),
CONTENT_OPTIONS,
(err, stdout) => { // eslint-disable-line handle-callback-err
(err, stdout, stderr) => {
testResults.push({
suite: 'Content Specs\t',
pass: testCount(stdout, /(\d+) passing/),
@@ -164,10 +185,103 @@ gulp.task('test:content:safe', ['test:prepare:build'], (cb) => {
pipe(runner);
});
gulp.task('test:karma', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(KARMA_TEST_COMMAND),
(err, stdout) => {
if (err) {
process.exit(1);
}
cb();
}
);
pipe(runner);
});
gulp.task('test:karma:watch', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(`${KARMA_TEST_COMMAND}:watch`),
(err, stdout) => {
cb(err);
}
);
pipe(runner);
});
gulp.task('test:karma:safe', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(KARMA_TEST_COMMAND),
(err, stdout) => {
testResults.push({
suite: 'Karma Specs\t',
pass: testCount(stdout, /(\d+) tests? completed/),
fail: testCount(stdout, /(\d+) tests? failed/),
pend: testCount(stdout, /(\d+) tests? skipped/),
});
cb();
}
);
pipe(runner);
});
gulp.task('test:e2e', ['test:prepare', 'test:prepare:server'], (cb) => {
let support = [
'Xvfb :99 -screen 0 1024x768x24 -extension RANDR',
testBin('npm run test:e2e:webdriver', 'DISPLAY=:99'),
].map(exec);
support.push(server);
Bluebird.all([
awaitPort(TEST_SERVER_PORT),
awaitPort(4444),
]).then(() => {
let runner = exec(
'npm run test:e2e',
(err, stdout, stderr) => {
support.forEach(kill);
if (err) {
process.exit(1);
}
cb();
}
);
pipe(runner);
});
});
gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => {
let support = [
'Xvfb :99 -screen 0 1024x768x24 -extension RANDR',
'npm run test:e2e:webdriver',
].map(exec);
Bluebird.all([
awaitPort(TEST_SERVER_PORT),
awaitPort(4444),
]).then(() => {
let runner = exec(
'npm run test:e2e',
(err, stdout, stderr) => {
let match = stdout.match(/(\d+) tests?.*(\d) failures?/);
testResults.push({
suite: 'End-to-End Specs\t',
pass: testCount(stdout, /(\d+) passing/),
fail: testCount(stdout, /(\d+) failing/),
pend: testCount(stdout, /(\d+) pending/),
});
support.forEach(kill);
cb();
}
);
pipe(runner);
});
});
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'),
(err) => {
(err, stdout, stderr) => {
if (err) {
process.exit(1);
}
@@ -186,7 +300,7 @@ 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'),
{maxBuffer: 500 * 1024},
(err) => {
(err, stdout, stderr) => {
if (err) {
process.exit(1);
}
@@ -206,7 +320,7 @@ gulp.task('test:api-v3:integration:separate-server', (done) => {
let runner = exec(
testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
{maxBuffer: 500 * 1024},
(err) => done(err)
(err, stdout, stderr) => done(err)
);
pipe(runner);
@@ -217,6 +331,7 @@ gulp.task('test', (done) => {
'test:sanity',
'test:content',
'test:common',
'test:karma',
'test:api-v3:unit',
'test:api-v3:integration',
done
+84 -81
View File
@@ -1,5 +1,6 @@
import fs from 'fs';
import _ from 'lodash';
import nconf from 'nconf';
import gulp from 'gulp';
import { postToSlack, conf } from './taskHelper';
@@ -11,82 +12,8 @@ const SLACK_CONFIG = {
const LOCALES = './website/common/locales/';
const ENGLISH_LOCALE = `${LOCALES}en/`;
function getArrayOfLanguages () {
let languages = fs.readdirSync(LOCALES);
languages.shift(); // Remove README.md from array of languages
return languages;
}
const ALL_LANGUAGES = getArrayOfLanguages();
function stripOutNonJsonFiles (collection) {
let onlyJson = _.filter(collection, (file) => {
return file.match(/[a-zA-Z]*\.json/);
});
return onlyJson;
}
function eachTranslationFile (languages, cb) {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
_.each(languages, (lang) => {
_.each(jsonFiles, (filename) => {
let parsedTranslationFile;
try {
const translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`);
parsedTranslationFile = JSON.parse(translationFile);
} catch (err) {
return cb(err);
}
let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename);
let parsedEnglishFile = JSON.parse(englishFile);
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
});
});
}
function eachTranslationString (languages, cb) {
eachTranslationFile(languages, (error, language, filename, englishJSON, translationJSON) => {
if (error) return;
_.each(englishJSON, (string, key) => {
const translationString = translationJSON[key];
cb(language, filename, key, string, translationString);
});
});
}
function formatMessageForPosting (msg, items) {
let body = `*Warning:* ${msg}`;
body += '\n\n```\n';
body += items.join('\n');
body += '\n```';
return body;
}
function getStringsWith (json, interpolationRegex) {
let strings = {};
_.each(json, (fileName) => {
const rawFile = fs.readFileSync(ENGLISH_LOCALE + fileName);
const parsedJson = JSON.parse(rawFile);
strings[fileName] = {};
_.each(parsedJson, (value, key) => {
const match = value.match(interpolationRegex);
if (match) strings[fileName][key] = match;
});
});
return strings;
}
const malformedStringExceptions = {
messageDropFood: true,
armoireFood: true,
@@ -96,6 +23,7 @@ const malformedStringExceptions = {
gulp.task('transifex', ['transifex:missingFiles', 'transifex:missingStrings', 'transifex:malformedStrings']);
gulp.task('transifex:missingFiles', () => {
let missingStrings = [];
eachTranslationFile(ALL_LANGUAGES, (error) => {
@@ -112,6 +40,7 @@ gulp.task('transifex:missingFiles', () => {
});
gulp.task('transifex:missingStrings', () => {
let missingStrings = [];
eachTranslationString(ALL_LANGUAGES, (language, filename, key, englishString, translationString) => {
@@ -129,6 +58,7 @@ gulp.task('transifex:missingStrings', () => {
});
gulp.task('transifex:malformedStrings', () => {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
let interpolationRegex = /<%= [a-zA-Z]* %>/g;
let stringsToLookFor = getStringsWith(jsonFiles, interpolationRegex);
@@ -136,23 +66,25 @@ gulp.task('transifex:malformedStrings', () => {
let stringsWithMalformedInterpolations = [];
let stringsWithIncorrectNumberOfInterpolations = [];
_.each(ALL_LANGUAGES, (lang) => {
_.each(stringsToLookFor, (strings, filename) => {
let translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`);
let count = 0;
_.each(ALL_LANGUAGES, function (lang) {
_.each(stringsToLookFor, function (strings, file) {
let translationFile = fs.readFileSync(LOCALES + lang + '/' + file);
let parsedTranslationFile = JSON.parse(translationFile);
_.each(strings, (value, key) => { // eslint-disable-line max-nested-callbacks
_.each(strings, function (value, key) {
let translationString = parsedTranslationFile[key];
if (!translationString) return;
let englishOccurences = stringsToLookFor[filename][key];
let englishOccurences = stringsToLookFor[file][key];
let translationOccurences = translationString.match(interpolationRegex);
if (!translationOccurences) {
let malformedString = `${lang} - ${filename} - ${key} - ${translationString}`;
let malformedString = `${lang} - ${file} - ${key} - ${translationString}`;
stringsWithMalformedInterpolations.push(malformedString);
} else if (englishOccurences.length !== translationOccurences.length && !malformedStringExceptions[key]) {
let missingInterpolationString = `${lang} - ${filename} - ${key} - ${translationString}`;
let missingInterpolationString = `${lang} - ${file} - ${key} - ${translationString}`;
stringsWithIncorrectNumberOfInterpolations.push(missingInterpolationString);
}
});
@@ -171,3 +103,74 @@ gulp.task('transifex:malformedStrings', () => {
postToSlack(formattedMessage, SLACK_CONFIG);
}
});
function getArrayOfLanguages () {
let languages = fs.readdirSync(LOCALES);
languages.shift(); // Remove README.md from array of languages
return languages;
}
function eachTranslationFile (languages, cb) {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
_.each(languages, (lang) => {
_.each(jsonFiles, (filename) => {
try {
var translationFile = fs.readFileSync(LOCALES + lang + '/' + filename);
var parsedTranslationFile = JSON.parse(translationFile);
} catch (err) {
return cb(err);
}
let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename);
let parsedEnglishFile = JSON.parse(englishFile);
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
});
});
}
function eachTranslationString (languages, cb) {
eachTranslationFile(languages, (error, language, filename, englishJSON, translationJSON) => {
if (error) return;
_.each(englishJSON, (string, key) => {
var translationString = translationJSON[key];
cb(language, filename, key, string, translationString);
});
});
}
function formatMessageForPosting (msg, items) {
let body = `*Warning:* ${msg}`;
body += '\n\n```\n';
body += items.join('\n');
body += '\n```';
return body;
}
function getStringsWith (json, interpolationRegex) {
var strings = {};
_.each(json, function (file_name) {
var raw_file = fs.readFileSync(ENGLISH_LOCALE + file_name);
var parsed_json = JSON.parse(raw_file);
strings[file_name] = {};
_.each(parsed_json, function (value, key) {
var match = value.match(interpolationRegex);
if (match) strings[file_name][key] = match;
});
});
return strings;
}
function stripOutNonJsonFiles (collection) {
let onlyJson = _.filter(collection, (file) => {
return file.match(/[a-zA-Z]*\.json/);
});
return onlyJson;
}
+20 -29
View File
@@ -12,7 +12,7 @@ import { resolve } from 'path';
* Get access to configruable values
*/
nconf.argv().env().file({ file: 'config.json' });
export const conf = nconf;
export var conf = nconf;
/*
* Kill a child process and any sub-children that process may have spawned.
@@ -26,12 +26,11 @@ export function kill (proc) {
pids.forEach(kill); return;
}
try {
exec(/^win/.test(process.platform) ?
`taskkill /PID ${pid} /T /F` :
`kill -9 ${pid}`);
} catch (e) {
console.log(e); // eslint-disable-line no-console
exec(/^win/.test(process.platform)
? `taskkill /PID ${pid} /T /F`
: `kill -9 ${pid}`);
}
catch (e) { console.log(e); }
});
};
@@ -45,25 +44,21 @@ export function kill (proc) {
* before failing.
*/
export function awaitPort (port, max = 60) {
return new Bluebird((rej, res) => {
let socket;
let timeout;
let interval;
return new Bluebird((reject, resolve) => {
let socket, timeout, interval;
timeout = setTimeout(() => {
clearInterval(interval);
rej(`Timed out after ${max} seconds`);
reject(`Timed out after ${max} seconds`);
}, max * 1000);
interval = setInterval(() => {
socket = net.connect({port}, () => {
socket = net.connect({port: port}, () => {
clearInterval(interval);
clearTimeout(timeout);
socket.destroy();
res();
}).on('error', () => {
socket.destroy();
});
resolve();
}).on('error', () => { socket.destroy; });
}, 1000);
});
}
@@ -72,12 +67,8 @@ export function awaitPort (port, max = 60) {
* Pipe the child's stdin and stderr to the parent process.
*/
export function pipe (child) {
child.stdout.on('data', (data) => {
process.stdout.write(data);
});
child.stderr.on('data', (data) => {
process.stderr.write(data);
});
child.stdout.on('data', (data) => { process.stdout.write(data); });
child.stderr.on('data', (data) => { process.stderr.write(data); });
}
/*
@@ -87,8 +78,8 @@ export function postToSlack (msg, config = {}) {
let slackUrl = nconf.get('SLACK_URL');
if (!slackUrl) {
console.error('No slack post url specified. Your message was:'); // eslint-disable-line no-console
console.log(msg); // eslint-disable-line no-console
console.error('No slack post url specified. Your message was:');
console.log(msg);
return;
}
@@ -98,15 +89,15 @@ export function postToSlack (msg, config = {}) {
channel: `#${config.channel || '#general'}`,
username: config.username || 'gulp task',
text: msg,
icon_emoji: `:${config.emoji || 'gulp'}:`, // eslint-disable-line camelcase
icon_emoji: `:${config.emoji || 'gulp'}:`,
})
.end((err) => {
if (err) console.error('Unable to post to slack', err); // eslint-disable-line no-console
.end((err, res) => {
if (err) console.error('Unable to post to slack', err);
});
}
export function runMochaTests (files, server, cb) {
require('../test/helpers/globals.helper'); // eslint-disable-line global-require
require('../test/helpers/globals.helper');
let mocha = new Mocha({reporter: 'spec'});
let tests = glob(files);
@@ -117,7 +108,7 @@ export function runMochaTests (files, server, cb) {
});
mocha.run((numberOfFailures) => {
if (!process.env.RUN_INTEGRATION_TEST_FOREVER) { // eslint-disable-line no-process-env
if (!process.env.RUN_INTEGRATION_TEST_FOREVER) {
if (server) kill(server);
process.exit(numberOfFailures);
}
+7 -5
View File
@@ -8,10 +8,12 @@
require('babel-register');
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
require('./gulp/gulp-apidoc'); // eslint-disable-line global-require
require('./gulp/gulp-build'); // eslint-disable-line global-require
if (process.env.NODE_ENV === 'production') {
require('./gulp/gulp-apidoc');
require('./gulp/gulp-newstuff');
require('./gulp/gulp-build');
require('./gulp/gulp-babelify');
} else {
require('glob').sync('./gulp/gulp-*').forEach(require); // eslint-disable-line global-require
require('gulp').task('default', ['test']); // eslint-disable-line global-require
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
@@ -16,7 +16,7 @@ var migrationName = '20140831_increase_gems_for_previous_contributions';
* https://github.com/HabitRPG/habitrpg/issues/3933
* Increase Number of Gems for Contributors
* author: Alys (d904bd62-da08-416b-a816-ba797c9ee265)
*
*
* Increase everyone's gems per their contribution level.
* Originally they were given 2 gems per tier.
* Now they are given 3 gems per tier for tiers 1,2,3
@@ -70,7 +70,7 @@ dbUsers.findEach(query, fields, function(err, user) {
var extraGems = tier; // tiers 1,2,3
if (tier > 3) { extraGems = 3 + (tier - 3) * 2; }
if (tier == 8) { extraGems = 11; }
var extraBalance = extraGems / 4;
extraBalance = extraGems / 4;
set['balance'] = user.balance + extraBalance;
// Capture current state of user:
@@ -39,7 +39,7 @@ function findUsers(gt){
console.log('User: ', countUsers, user._id);
var update = {
$set: {}
$set: {};
};
if(user.auth && user.auth.local) {
@@ -60,4 +60,4 @@ function findUsers(gt){
});
};
findUsers();
findUsers();
-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;
-128
View File
@@ -1,128 +0,0 @@
import { selectGearToPin } from '../website/common/script/ops/pinnedGearUtils';
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 = selectGearToPin(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;
-111
View File
@@ -1,111 +0,0 @@
var migrationName = '20171030_jackolanterns.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 the Jack-O'-Lantern ladder:
* Ghost Jack-O-Lantern Mount to owners of Ghost Jack-O-Lantern Pet
* Ghost Jack-O-Lantern Pet to owners of Jack-O-Lantern Mount
* Jack-O-Lantern Mount to owners of Jack-O-Lantern Pet
* Jack-O-Lantern Pet 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.pets',
'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 = {};
var inc = {
'items.food.Candy_Skeleton': 1,
'items.food.Candy_Base': 1,
'items.food.Candy_CottonCandyBlue': 1,
'items.food.Candy_CottonCandyPink': 1,
'items.food.Candy_Shade': 1,
'items.food.Candy_White': 1,
'items.food.Candy_Golden': 1,
'items.food.Candy_Zombie': 1,
'items.food.Candy_Desert': 1,
'items.food.Candy_Red': 1,
};
if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Ghost']) {
set = {'migration':migrationName, 'items.mounts.JackOLantern-Ghost': true};
} else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Base']) {
set = {'migration':migrationName, 'items.pets.JackOLantern-Ghost': 5};
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Base']) {
set = {'migration':migrationName, 'items.mounts.JackOLantern-Base': true};
} else {
set = {'migration':migrationName, 'items.pets.JackOLantern-Base': 5};
}
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;
-128
View File
@@ -1,128 +0,0 @@
var migrationName = '20171117_turkey_ladder.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 the Turkey Day ladder:
* Grant Turkey Costume to those who have the Gilded Turkey mount
* Grant Gilded Turkey mount to those who have the Gilded Turkey pet
* Grant Gilded Turkey pet to those who have the Base Turkey mount
* Grant Base Turkey mount to those who have the Base Turkey pet
* Grant Base Turkey pet to those who have none of the above yet
*/
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-11-01')},
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
'items.pets',
'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 && user.items && user.items.mounts && user.items.mounts['Turkey-Gilded']) {
set = {
migration: migrationName,
'items.gear.owned.head_special_turkeyHelmBase': false,
'items.gear.owned.armor_special_turkeyArmorBase': false,
'items.gear.owned.back_special_turkeyTailBase': false,
};
var push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_turkeyHelmBase',
_id: monk.id(),
},
{
type: 'marketGear',
path: 'gear.flat.armor_special_turkeyArmorBase',
_id: monk.id(),
},
{
type: 'marketGear',
path: 'gear.flat.back_special_turkeyTailBase',
_id: monk.id(),
},
];
} else if (user && user.items && user.items.pets && user.items.pets['Turkey-Gilded']) {
set = {'migration':migrationName, 'items.mounts.Turkey-Gilded':true};
} else if (user && user.items && user.items.mounts && user.items.mounts['Turkey-Base']) {
set = {'migration':migrationName, 'items.pets.Turkey-Gilded':5};
} else if (user && user.items && user.items.pets && user.items.pets['Turkey-Base']) {
set = {'migration':migrationName, 'items.mounts.Turkey-Base':true};
} else {
set = {'migration':migrationName, 'items.pets.Turkey-Base':5};
}
dbUsers.update({_id: user._id}, {$set: set});
if (push) {
dbUsers.update({_id: user._id}, {$push: {pinnedItems: {$each: push}}});
}
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
}
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
module.exports = processUsers;
-103
View File
@@ -1,103 +0,0 @@
var migrationName = '20171230_nye_hats.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 New Year's Eve party hats to users in sequence
*/
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-11-30')},
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
'items.gear.owned',
] // 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 push = {};
if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2017':false};
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_nye2017', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2016':false};
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_nye2016', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2015':false};
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_nye2015', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2014':false};
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_nye2014', '_id': monk.id()}};
} else {
set = {'migration':migrationName, 'items.gear.owned.head_special_nye':false};
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_nye', '_id': monk.id()}};
}
dbUsers.update({_id: user._id}, {$set: set, $push: push});
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
}
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
module.exports = processUsers;
@@ -1,79 +0,0 @@
/*
* Convert purchased.plan.nextPaymentProcessing from a double to a date field for Apple 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 });
function processUsers(lastId) {
// specify a query to limit the affected users (empty for all users):
var query = {
'purchased.plan.paymentMethod': "Apple",
'purchased.plan.nextPaymentProcessing': {$type: 'double'},
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
})
.then(updateUsers)
.catch(function (err) {
console.log(err);
return exiting(1, 'ERROR! ' + err);
});
}
var progressCount = 100;
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 = {
'purchased.plan.nextPaymentProcessing': new Date(user.purchased.plan.nextPaymentProcessing),
};
dbUsers.update({_id: user._id}, {$set: set});
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
}
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
module.exports = processUsers;
@@ -1,93 +0,0 @@
const UserNotification = require('../website/server/models/userNotification').model;
const content = require('../website/common/script/content/index');
const migrationName = '20180125_clean_new_migrations';
const authorName = 'paglias'; // in case script author needs to know when their ...
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
/*
* Clean new migration types for processed users
*/
const monk = require('monk');
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
const dbUsers = monk(connectionString).get('users', { castIds: false });
const progressCount = 1000;
let count = 0;
function updateUser (user) {
count++;
const types = ['NEW_MYSTERY_ITEMS', 'CARD_RECEIVED', 'NEW_CHAT_MESSAGE'];
dbUsers.update({_id: user._id}, {
$pull: {notifications: { type: {$in: types } } },
$set: {migration: migrationName},
});
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`);
}
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);
}
function displayData () {
console.warn(`\n${ count } users processed\n`);
return exiting(0);
}
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
const userPromises = users.map(updateUser);
const lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => {
processUsers(lastUser._id);
});
}
function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
const query = {
migration: {$ne: migrationName},
'auth.timestamps.loggedin': {$gt: new Date('2010-01-24')},
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}
module.exports = processUsers;
-149
View File
@@ -1,149 +0,0 @@
const UserNotification = require('../website/server/models/userNotification').model;
const content = require('../website/common/script/content/index');
const migrationName = '20180125_migrations-v2';
const authorName = 'paglias'; // in case script author needs to know when their ...
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
/*
* Migrate to new notifications system
*/
const monk = require('monk');
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
const dbUsers = monk(connectionString).get('users', { castIds: false });
const progressCount = 1000;
let count = 0;
function updateUser (user) {
count++;
const notifications = [];
// UNALLOCATED_STATS_POINTS skipped because added on each save
// NEW_STUFF skipped because it's a new type
// GROUP_TASK_NEEDS_WORK because it's a new type
// NEW_INBOX_MESSAGE not implemented yet
// NEW_MYSTERY_ITEMS
const mysteryItems = user.purchased && user.purchased.plan && user.purchased.plan.mysteryItems;
if (Array.isArray(mysteryItems) && mysteryItems.length > 0) {
const newMysteryNotif = new UserNotification({
type: 'NEW_MYSTERY_ITEMS',
data: {
items: mysteryItems,
},
}).toJSON();
notifications.push(newMysteryNotif);
}
// CARD_RECEIVED
Object.keys(content.cardTypes).forEach(cardType => {
const existingCards = user.items.special[`${cardType}Received`] || [];
existingCards.forEach(sender => {
const newNotif = new UserNotification({
type: 'CARD_RECEIVED',
data: {
card: cardType,
from: {
// id is missing in old notifications
name: sender,
},
},
}).toJSON();
notifications.push(newNotif);
});
});
// NEW_CHAT_MESSAGE
Object.keys(user.newMessages).forEach(groupId => {
const existingNotif = user.newMessages[groupId];
if (existingNotif) {
const newNotif = new UserNotification({
type: 'NEW_CHAT_MESSAGE',
data: {
group: {
id: groupId,
name: existingNotif.name,
},
},
}).toJSON();
notifications.push(newNotif);
}
});
dbUsers.update({_id: user._id}, {
$push: {notifications: { $each: notifications } },
$set: {migration: migrationName},
});
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`);
}
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);
}
function displayData () {
console.warn(`\n${ count } users processed\n`);
return exiting(0);
}
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
const userPromises = users.map(updateUser);
const lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => {
processUsers(lastUser._id);
});
}
function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
const query = {
migration: {$ne: migrationName},
'auth.timestamps.loggedin': {$gt: new Date('2010-01-24')},
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}
module.exports = processUsers;
-118
View File
@@ -1,118 +0,0 @@
var migrationName = '20180130_habit_birthday.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 party robes: most recent user doesn't have of 2014-2018. Also cake!
*/
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('2018-01-01')}, // remove after first run to cover remaining users
};
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)
'items.gear.owned'
],
})
.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 push;
var set = {'migration':migrationName};
if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday2017')) {
set['items.gear.owned.armor_special_birthday2018'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2018', '_id': monk.id()}};
} else if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday2016')) {
set['items.gear.owned.armor_special_birthday2017'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2017', '_id': monk.id()}};
} else if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday2015')) {
set['items.gear.owned.armor_special_birthday2016'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2016', '_id': monk.id()}};
} else if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday')) {
set['items.gear.owned.armor_special_birthday2015'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2015', '_id': monk.id()}};
} else {
set['items.gear.owned.armor_special_birthday'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday', '_id': monk.id()}};
}
var inc = {
'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,
'achievements.habitBirthdays':1
};
dbUsers.update({_id: user._id}, {$set: set, $inc: inc, $push: push});
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
}
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
module.exports = processUsers;
+47 -47
View File
@@ -1,47 +1,47 @@
import Bluebird from 'Bluebird';
import { model as Challenges } from '../../website/server/models/challenge';
import { model as User } from '../../website/server/models/user';
async function syncChallengeToMembers (challenges) {
let challengSyncPromises = challenges.map(async function (challenge) {
let users = await User.find({
// _id: '',
challenges: challenge._id,
}).exec();
let promises = [];
users.forEach(function (user) {
promises.push(challenge.syncToUser(user));
promises.push(challenge.save());
promises.push(user.save());
});
return Bluebird.all(promises);
});
return await Bluebird.all(challengSyncPromises);
}
async function syncChallenges (lastChallengeDate) {
let query = {
// _id: '',
};
if (lastChallengeDate) {
query.createdOn = { $lte: lastChallengeDate };
}
let challengesFound = await Challenges.find(query)
.limit(10)
.sort('-createdAt')
.exec();
let syncedChallenges = await syncChallengeToMembers(challengesFound)
.catch(reason => console.error(reason));
let lastChallenge = challengesFound[challengesFound.length - 1];
if (lastChallenge) syncChallenges(lastChallenge.createdAt);
return syncedChallenges;
};
module.exports = syncChallenges;
import Bluebird from 'Bluebird';
import { model as Challenges } from '../../website/server/models/challenge';
import { model as User } from '../../website/server/models/user';
async function syncChallengeToMembers (challenges) {
let challengSyncPromises = challenges.map(async function (challenge) {
let users = await User.find({
// _id: '',
challenges: challenge._id,
}).exec();
let promises = [];
users.forEach(function (user) {
promises.push(challenge.syncToUser(user));
promises.push(challenge.save());
promises.push(user.save());
});
return Bluebird.all(promises);
});
return await Bluebird.all(challengSyncPromises);
}
async function syncChallenges (lastChallengeDate) {
let query = {
// _id: '',
};
if (lastChallengeDate) {
query.createdOn = { $lte: lastChallengeDate };
}
let challengesFound = await Challenges.find(query)
.limit(10)
.sort('-createdAt')
.exec();
let syncedChallenges = await syncChallengeToMembers(challengesFound)
.catch(reason => console.error(reason));
let lastChallenge = challengesFound[challengesFound.length - 1];
if (lastChallenge) syncChallenges(lastChallenge.createdAt);
return syncedChallenges;
};
module.exports = syncChallenges;
-59
View File
@@ -1,59 +0,0 @@
# Indexes
This file contains a list of indexes that are on Habitica's production Mongo server.
If we ever have an issue, use this list to reindex.
## Challenges
- `{ "group": 1, "official": -1, "timestamp": -1 }`
- `{ "leader": 1, "official": -1, "timestamp": -1 }`
- `{ "official": -1, "timestamp": -1 }`
## Groups
- `{ "privacy": 1, "type": 1, "memberCount": -1 }`
- `{ "privacy": 1 }`
- `{ "purchased.plan.customerId": 1 }`
- `{ "purchased.plan.paymentMethod": 1 }`
- `{ "purchased.plan.planId": 1, "purchased.plan.dateTerminated": 1 }`
- `{ "type": 1, "memberCount": -1, "_id": 1 }`
- `{ "type": 1 }`
## Tasks
- `{ "challenge.id": 1 }`
- `{ "challenge.taskId": 1 }`
- `{ "group.id": 1 }`
- `{ "group.taskId": 1 }`
- `{ "type": 1, "everyX": 1, "frequency": 1 }`
- `{ "userId": 1 }`
- `{ "yesterDaily": 1, "type": 1 }`
## Users
- `{ "_id": 1, "apiToken": 1 }`
- `{ "auth.facebook.emails.value": 1 }`
- `{ "auth.facebook.id": 1 }`
- `{ "auth.google.emails.value": 1 }`
- `{ "auth.google.id": 1 }`
- `{ "auth.local.email": 1 }`
- `{ "auth.local.lowerCaseUsername": 1 }`
- `{ "auth.local.username": 1 }`
- `{ "auth.timestamps.created": 1 }`
- `{ "auth.timestamps.loggedin": 1, "_lastPushNotification": 1, "preferences.timezoneOffset": 1 }`
- `{ "auth.timestamps.loggedin": 1 }`
- `{ "backer.tier": -1 }`
- `{ "challenges": 1, "_id": 1 }`
- `{ "contributor.admin": 1, "contributor.level": -1, "backer.npc": -1, "profile.name": 1 }`
- `{ "contributor.level": 1 }`
- `{ "flags.newStuff": 1 }`
- `{ "flags.armoireEmpty": 1 }`
- `{ "guilds": 1, "_id": 1 }`
- `{ "invitations.guilds.id": 1, "_id": 1 }`
- `{ "invitations.party.id": 1 }`
- `{ "loginIncentives": 1 }`
- `{ "migration": 1 }`
- `{ "party._id": 1, "_id": 1 }`
- `{ "preferences.sleep": 1, "_id": 1, "flags.lastWeeklyRecap": 1, "preferences.emailNotifications.unsubscribeFromAll": 1, "preferences.emailNotifications.weeklyRecaps": 1 }`
- `{ "preferences.sleep": 1, "_id": 1, "lastCron": 1, "preferences.emailNotifications.importantAnnouncements": 1, "preferences.emailNotifications.unsubscribeFromAll": 1, "flags.recaptureEmailsPhase": 1 }`
- `{ "profile.name": 1 }`
- `{ "purchased.plan.customerId": 1 }`
- `{ "purchased.plan.paymentMethod": 1 }`
- `{ "stats.score.overall": 1 }`
- `{ "webhooks.type": 1 }`
+5 -2
View File
@@ -17,5 +17,8 @@ function setUpServer () {
setUpServer();
// Replace this with your migration
const processUsers = require('./20180125_clean_new_notifications.js');
processUsers();
var processUsers = require('./groups/update-groups-with-group-plans');
processUsers()
.catch(function (err) {
console.log(err)
})
+2 -15
View File
@@ -1,23 +1,10 @@
var UserNotification = require('../website/server/models/userNotification').model
var _id = '';
var items = ['back_mystery_201801','headAccessory_mystery_201801']
var update = {
$addToSet: {
'purchased.plan.mysteryItems':{
$each: items,
$each:['body_mystery_201705','head_mystery_201705']
}
},
$push: {
notifications: (new UserNotification({
type: 'NEW_MYSTERY_ITEMS',
data: {
items: items,
},
})).toJSON(),
},
}
};
/*var update = {
-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);
});
+4 -14
View File
@@ -1,4 +1,4 @@
var migrationName = '20180102_takeThis.js'; // Update per month
var migrationName = '20170502_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
@@ -7,14 +7,14 @@ var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
*/
var monk = require('monk');
var connectionString = 'mongodb://sabrecat:z8e8jyRA8CTofMQ@ds013393-a0.mlab.com:13393/habitica?auto_reconnect=true';
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},
'challenges':{$in:['5f70ce5b-2d82-4114-8e44-ca65615aae62']} // Update per month
'challenges':{$in:['69999331-d4ea-45a0-8c3f-f725d22b56c8']} // Update per month
};
if (lastId) {
@@ -65,29 +65,19 @@ function updateUser (user) {
set = {'migration':migrationName};
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.back_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.back_special_takeThis', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.body_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_takeThis', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.head_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_takeThis', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.armor_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_takeThis', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.weapon_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.weapon_special_takeThis', '_id': monk.id()}};
} else {
set = {'migration':migrationName, 'items.gear.owned.shield_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.shield_special_takeThis', '_id': monk.id()}};
}
if (push) {
dbUsers.update({_id: user._id}, {$set: set, $push: push});
} else {
dbUsers.update({_id: user._id}, {$set: set});
}
dbUsers.update({_id: user._id}, {$set:set});
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
-88
View File
@@ -1,88 +0,0 @@
var migrationName = 'tasks-set-everyX';
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
/*
* Iterates over all tasks and sets invalid everyX values (less than 0 or more than 9999 or not an int) field to 0
*/
var monk = require('monk');
var connectionString = 'mongodb://sabrecat:z8e8jyRA8CTofMQ@ds013393-a0.mlab.com:13393/habitica?auto_reconnect=true';
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 = {
type: "daily",
everyX: {
$not: {
$gte: 0,
$lte: 9999,
$type: "int",
}
},
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbTasks.find(query, {
sort: {_id: 1},
limit: 250,
fields: [],
})
.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 = {'everyX': 0};
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;
@@ -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;
-38
View File
@@ -1,38 +0,0 @@
var migrationName = 'AccountTransfer';
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
*/
const monk = require('monk');
const connectionString = '';
const Users = monk(connectionString).get('users', { castIds: false });
import uniq from 'lodash/uniq';
import Bluebird from 'bluebird';
module.exports = async function accountTransfer () {
const fromAccountId = '';
const toAccountId = '';
const fromAccount = await Users.findOne({_id: fromAccountId});
const toAccount = await Users.findOne({_id: toAccountId});
const newMounts = Object.assign({}, fromAccount.items.mounts, toAccount.items.mounts);
const newPets = Object.assign({}, fromAccount.items.pets, toAccount.items.pets);
const newBackgrounds = Object.assign({}, fromAccount.purchased.background, toAccount.purchased.background);
await Users.update({_id: toAccountId}, {
$set: {
'items.pets': newPets,
'items.mounts': newMounts,
'purchased.background': newBackgrounds,
},
})
.then((result) => {
console.log(result);
});
};
-93
View File
@@ -1,93 +0,0 @@
const migrationName = 'AchievementRestore';
const authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
const authorUuid = ''; //... own data is done
/*
* This migraition will copy user data from prod to test
*/
import Bluebird from 'bluebird';
const monk = require('monk');
const connectionString = 'mongodb://localhost/new-habit';
const Users = monk(connectionString).get('users', { castIds: false });
const monkOld = require('monk');
const oldConnectionSting = 'mongodb://localhost/old-habit';
const UsersOld = monk(oldConnectionSting).get('users', { castIds: false });
function getAchievementUpdate (newUser, oldUser) {
const oldAchievements = oldUser.achievements;
const newAchievements = newUser.achievements;
let achievementsUpdate = Object.assign({}, newAchievements);
// ultimateGearSets
if (!achievementsUpdate.ultimateGearSets && oldAchievements.ultimateGearSets) {
achievementsUpdate.ultimateGearSets = oldAchievements.ultimateGearSets;
} else if (oldAchievements.ultimateGearSets) {
for (let index in oldAchievements.ultimateGearSets) {
if (oldAchievements.ultimateGearSets[index]) achievementsUpdate.ultimateGearSets[index] = true;
}
}
// challenges
if (!newAchievements.challenges) newAchievements.challenges = [];
if (!oldAchievements.challenges) oldAchievements.challenges = [];
achievementsUpdate.challenges = newAchievements.challenges.concat(oldAchievements.challenges);
// Quests
if (!achievementsUpdate.quests) achievementsUpdate.quests = {};
for (let index in oldAchievements.quests) {
if (!achievementsUpdate.quests[index]) {
achievementsUpdate.quests[index] = oldAchievements.quests[index];
} else {
achievementsUpdate.quests[index] += oldAchievements.quests[index];
}
}
// Rebirth level
if (achievementsUpdate.rebirthLevel) {
achievementsUpdate.rebirthLevel = Math.max(achievementsUpdate.rebirthLevel, oldAchievements.rebirthLevel);
} else if (oldAchievements.rebirthLevel) {
achievementsUpdate.rebirthLevel = oldAchievements.rebirthLevel;
}
//All others
const indexsToIgnore = ['ultimateGearSets', 'challenges', 'quests', 'rebirthLevel'];
for (let index in oldAchievements) {
if (indexsToIgnore.indexOf(index) !== -1) continue;
if (!achievementsUpdate[index]) {
achievementsUpdate[index] = oldAchievements[index];
continue;
}
if (Number.isInteger(oldAchievements[index])) {
achievementsUpdate[index] += oldAchievements[index];
} else {
if (oldAchievements[index] === true) achievementsUpdate[index] = true;
}
}
return achievementsUpdate;
}
module.exports = async function achievementRestore () {
const userIds = [
];
for (let index in userIds) {
const userId = userIds[index];
const oldUser = await UsersOld.findOne({_id: userId}, 'achievements');
const newUser = await Users.findOne({_id: userId}, 'achievements');
const achievementUpdate = getAchievementUpdate(newUser, oldUser);
await Users.update(
{_id: userId},
{
$set: {
'achievements': achievementUpdate,
},
});
console.log(`Updated ${userId}`);
}
};
-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();
};
+13674
View File
File diff suppressed because it is too large Load Diff
-19974
View File
File diff suppressed because it is too large Load Diff
+60 -27
View File
@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.24.6",
"version": "3.96.0",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",
@@ -14,7 +14,6 @@
"autoprefixer": "^6.4.0",
"aws-sdk": "^2.0.25",
"axios": "^0.16.0",
"axios-progress-bar": "^0.1.7",
"babel-core": "^6.0.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^6.0.0",
@@ -27,70 +26,88 @@
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.6.0",
"babel-runtime": "^6.11.6",
"babelify": "^7.2.0",
"bcrypt": "^1.0.2",
"bluebird": "^3.3.5",
"body-parser": "^1.15.0",
"bootstrap": "^4.0.0",
"bootstrap-vue": "^1.5.0",
"bootstrap": "^4.0.0-alpha.6",
"bootstrap-vue": "^0.15.8",
"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",
"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",
"file-loader": "^0.10.0",
"glob": "^4.3.5",
"got": "^6.1.1",
"grunt": "~0.4.1",
"grunt-cli": "~0.1.9",
"grunt-contrib-clean": "~0.6.0",
"grunt-contrib-copy": "~0.6.0",
"grunt-contrib-cssmin": "~0.10.0",
"grunt-contrib-stylus": "~0.20.0",
"grunt-contrib-uglify": "~0.6.0",
"grunt-contrib-watch": "~0.6.1",
"grunt-hashres": "habitrpg/grunt-hashres#v0.4.2",
"gulp": "^3.9.0",
"gulp-babel": "^6.1.2",
"gulp-grunt": "^0.5.2",
"gulp-imagemin": "^2.4.0",
"gulp-nodemon": "^2.0.4",
"gulp-sourcemaps": "^1.6.0",
"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",
"merge-stream": "^1.0.0",
"method-override": "^2.3.5",
"moment": "^2.13.0",
"moment-recur": "git://github.com/habitrpg/moment-recur.git#f147ef27bbc26ca67638385f3db4a44084c76626",
"moment-recur": "habitrpg/moment-recur#v1.0.6",
"mongoose": "^4.8.6",
"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",
"pageres": "^4.1.1",
"passport": "^0.3.2",
"passport-facebook": "^2.0.0",
"passport-google-oauth20": "1.0.0",
"paypal-ipn": "3.0.0",
"paypal-rest-sdk": "^1.8.1",
"popper.js": "^1.13.0",
"paypal-rest-sdk": "^1.2.1",
"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.git#6bc2b5fdb1bdc9649b9ec1964d79ca50187fc8a9",
"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",
"serve-favicon": "^2.3.0",
"shelljs": "^0.7.6",
"stripe": "^4.2.0",
"superagent": "^3.4.3",
@@ -102,14 +119,14 @@
"useragent": "^2.1.9",
"uuid": "^3.0.1",
"validator": "^4.9.0",
"vue": "^2.5.2",
"vue-loader": "^13.3.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"vue": "^2.1.0",
"vue-loader": "^11.0.0",
"vue-mugen-scroll": "^0.2.1",
"vue-router": "^3.0.0",
"vue-router": "^2.0.0-rc.5",
"vue-style-loader": "^3.0.0",
"vue-template-compiler": "^2.5.2",
"vuedraggable": "^2.15.0",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
"vue-template-compiler": "^2.1.10",
"webpack": "^2.2.1",
"webpack-merge": "^4.0.0",
"winston": "^2.1.0",
@@ -119,11 +136,11 @@
"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 && gulp apidoc",
"test": "npm run lint && gulp test && npm run client:unit && gulp apidoc",
"test:build": "gulp test:prepare:build",
"test:api-v3": "gulp test:api-v3",
"test:api-v3:unit": "gulp test:api-v3:unit",
@@ -132,17 +149,22 @@
"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: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",
"test:e2e:webdriver": "webdriver-manager start",
"test:e2e": "protractor test/client-old/e2e/protractor.conf.js",
"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": "node webpack/dev-server.js",
"client:build": "gulp build:client",
"client:dev": "gulp bootstrap && node webpack/dev-server.js",
"client:build": "gulp bootstrap && 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 nodemon",
"postinstall": "gulp build",
"start": "gulp run:dev",
"postinstall": "bower --config.interactive=false install -f && gulp build && npm run client:build",
"apidoc": "gulp apidoc"
},
"devDependencies": {
@@ -153,17 +175,22 @@
"chromedriver": "^2.27.2",
"connect-history-api-fallback": "^1.1.0",
"coveralls": "^2.11.2",
"cross-env": "^4.0.0",
"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-friendly-formatter": "^2.0.5",
"eslint-loader": "^1.3.0",
"eslint-plugin-html": "^2.0.0",
"eslint-plugin-mocha": "^4.7.0",
"event-stream": "^3.2.2",
"eventsource-polyfill": "^0.9.6",
"expect.js": "~0.2.0",
"grunt-karma": "~0.12.1",
"http-proxy-middleware": "^0.17.0",
"inject-loader": "^3.0.0-beta4",
"istanbul": "^1.1.0-alpha.1",
"karma": "^1.3.0",
"karma-babel-preprocessor": "^6.0.1",
@@ -178,17 +205,23 @@
"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",
"mongodb": "^2.2.33",
"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",
"sinon": "^4.2.2",
"sinon": "^1.17.2",
"sinon-chai": "^2.8.0",
"sinon-stub-promise": "^4.0.0",
"superagent-defaults": "^0.1.13",
"vinyl-transform": "^1.0.0",
"webpack-bundle-analyzer": "^2.2.1",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.6.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;
}
@@ -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,
@@ -64,11 +64,11 @@ describe('GET /challenges/:challengeId/export/csv', () => {
let sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
let splitRes = res.split('\n');
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Task,Value,Notes');
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
expect(splitRes[5]).to.equal('');
});
});
@@ -142,25 +142,4 @@ describe('GET /challenges/:challengeId/members', () => {
let resIds = res.concat(res2).map(member => member._id);
expect(resIds).to.eql(expectedIds.sort());
});
it('supports using req.query.search to get search members', async () => {
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
let challenge = await generateChallenge(user, group);
let usersToGenerate = [];
for (let i = 0; i < 3; i++) {
usersToGenerate.push(generateUser({
challenges: [challenge._id],
'profile.name': `${i}profilename`,
}));
}
let generatedUsers = await Promise.all(usersToGenerate);
let profileNames = generatedUsers.map(generatedUser => generatedUser.profile.name);
let firstProfileName = profileNames[0];
let nameToSearch = firstProfileName.substring(0, 4);
let response = await user.get(`/challenges/${challenge._id}/members?search=${nameToSearch}`);
expect(response[0].profile.name).to.eql(firstProfileName);
});
});
@@ -95,23 +95,13 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
expect(memberProgress.tasks[0].challenge.taskId).to.equal(chalTasks[0]._id);
});
it('returns the tasks without the tags and checklist', async () => {
it('returns the tasks without the tags', async () => {
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
let challenge = await generateChallenge(user, group);
let taskText = 'Test Text';
await user.post(`/tasks/challenge/${challenge._id}`, [{
type: 'todo',
text: taskText,
checklist: [
{
_id: 123,
text: 'test',
},
],
}]);
await user.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: taskText}]);
let memberProgress = await user.get(`/challenges/${challenge._id}/members/${user._id}`);
expect(memberProgress.tasks[0]).not.to.have.key('tags');
expect(memberProgress.tasks[0].checklist).to.eql([]);
});
});
@@ -4,7 +4,6 @@ import {
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { TAVERN_ID } from '../../../../../website/common/script/constants';
describe('GET challenges/groups/:groupId', () => {
context('Public Guild', () => {
@@ -182,123 +181,4 @@ describe('GET challenges/groups/:groupId', () => {
expect(foundChallengeIndex).to.eql(1);
});
});
context('Party', () => {
let party, user, nonMember, challenge, challenge2;
before(async () => {
let { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestParty',
type: 'party',
},
});
party = group;
user = groupLeader;
nonMember = await generateUser();
challenge = await generateChallenge(user, group);
challenge2 = await generateChallenge(user, group);
});
it('should prevent non-member from seeing challenges', async () => {
await expect(nonMember.get(`/challenges/groups/${party._id}`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('groupNotFound'),
});
});
it('should return group challenges for member with populated leader', async () => {
let challenges = await user.get(`/challenges/groups/${party._id}`);
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
});
});
it('should return group challenges for member using ID "party"', async () => {
let challenges = await user.get('/challenges/groups/party');
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
});
});
});
context('Tavern', () => {
let tavern, user, challenge, challenge2;
before(async () => {
user = await generateUser();
await user.update({balance: 0.5});
tavern = await user.get(`/groups/${TAVERN_ID}`);
challenge = await generateChallenge(user, tavern, {prize: 1});
challenge2 = await generateChallenge(user, tavern, {prize: 1});
});
it('should return tavern challenges with populated leader', async () => {
let challenges = await user.get(`/challenges/groups/${TAVERN_ID}`);
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
});
});
it('should return tavern challenges using ID "habitrpg', async () => {
let challenges = await user.get('/challenges/groups/habitrpg');
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
});
});
});
});
@@ -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',
},
@@ -101,21 +101,19 @@ describe('POST /challenges/:challengeId/join', () => {
});
it('syncs challenge tasks to joining user', async () => {
const taskText = 'A challenge task text';
let taskText = 'A challenge task text';
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
{type: 'daily', text: taskText},
{type: 'habit', text: taskText},
]);
await authorizedUser.post(`/challenges/${challenge._id}/join`);
const tasks = await authorizedUser.get('/tasks/user');
const syncedTask = tasks.find((task) => {
return task.text === taskText;
let tasks = await authorizedUser.get('/tasks/user');
let tasksTexts = tasks.map((task) => {
return task.text;
});
expect(syncedTask.text).to.eql(taskText);
expect(syncedTask.isDue).to.exist;
expect(syncedTask.nextDue).to.exist;
expect(tasksTexts).to.include(taskText);
});
it('adds challenge tag to user tags', async () => {
@@ -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 () => {
@@ -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;
@@ -149,19 +129,13 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
await sleep(0.5);
const tasks = await winningUser.get('/tasks/user');
const testTask = _.find(tasks, (task) => {
let tasks = await winningUser.get('/tasks/user');
let testTask = _.find(tasks, (task) => {
return task.text === taskText;
});
const updatedUser = await winningUser.sync();
const challengeTag = updatedUser.tags.find(tags => {
return tags.id === challenge._id;
});
expect(testTask.challenge.broken).to.eql('CHALLENGE_CLOSED');
expect(testTask.challenge.winner).to.eql(winningUser.profile.name);
expect(challengeTag.challenge).to.eql('false');
});
});
});
@@ -1,43 +0,0 @@
import {
generateUser,
generateGroup,
} from '../../../../helpers/api-v3-integration.helper';
describe('POST /challenges/:challengeId/clone', () => {
it('clones a challenge', async () => {
const user = await generateUser({balance: 10});
const group = await generateGroup(user);
const name = 'Test Challenge';
const shortName = 'TC Label';
const description = 'Test Description';
const prize = 1;
const challenge = await user.post('/challenges', {
group: group._id,
name,
shortName,
description,
prize,
});
const challengeTask = await user.post(`/tasks/challenge/${challenge._id}`, {
text: 'test habit',
type: 'habit',
up: false,
down: true,
notes: 1976,
});
const cloneChallengeResponse = await user.post(`/challenges/${challenge._id}/clone`, {
group: group._id,
name: `${name} cloned`,
shortName,
description,
prize,
});
expect(cloneChallengeResponse.clonedTasks[0].text).to.eql(challengeTask.text);
expect(cloneChallengeResponse.clonedTasks[0]._id).to.not.eql(challengeTask._id);
expect(cloneChallengeResponse.clonedTasks[0].challenge.id).to.eql(cloneChallengeResponse.clonedChallenge._id);
});
});
+8 -188
View File
@@ -1,6 +1,5 @@
import {
createAndPopulateGroup,
generateUser,
translate as t,
sleep,
server,
@@ -11,23 +10,11 @@ import {
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 guildsAllowingBannedWords from '../../../../../website/server/libs/guildsAllowingBannedWords';
import * as email from '../../../../../website/server/libs/email';
import { IncomingWebhook } from '@slack/client';
import nconf from 'nconf';
const BASE_URL = nconf.get('BASE_URL');
describe('POST /chat', () => {
let user, groupWithChat, member, additionalMember;
let testMessage = 'Test Message';
let testBannedWordMessage = 'TEST_PLACEHOLDER_SWEAR_WORD_HERE';
let testSlurMessage = 'message with TEST_PLACEHOLDER_SLUR_WORD_HERE';
let bannedWordErrorMessage = t('bannedWordUsed').split('.');
bannedWordErrorMessage[0] += ` (${removePunctuationFromString(testBannedWordMessage.toLowerCase())})`;
bannedWordErrorMessage = bannedWordErrorMessage.join('.');
before(async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({
@@ -38,6 +25,7 @@ describe('POST /chat', () => {
},
members: 2,
});
user = groupLeader;
groupWithChat = group;
member = members[0];
@@ -91,29 +79,11 @@ describe('POST /chat', () => {
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('returns an error when chat message contains a banned word in a public guild', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {
name: 'public guild',
type: 'guild',
privacy: 'public',
},
members: 1,
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('bannedWordUsed'),
});
await expect(members[0].post(`/groups/${group._id}/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 () => {
@@ -122,7 +92,7 @@ describe('POST /chat', () => {
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: bannedWordErrorMessage,
message: t('bannedWordUsed'),
});
});
@@ -132,26 +102,10 @@ describe('POST /chat', () => {
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: bannedWordErrorMessage,
message: t('bannedWordUsed'),
});
});
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});
@@ -181,7 +135,7 @@ describe('POST /chat', () => {
expect(message.message.id).to.exist;
});
it('does not error when sending a chat message containing a banned word to a public guild in which banned words are allowed', async () => {
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',
@@ -191,8 +145,6 @@ describe('POST /chat', () => {
members: 1,
});
guildsAllowingBannedWords[group._id] = true;
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
expect(message.message.id).to.exist;
@@ -214,114 +166,6 @@ describe('POST /chat', () => {
});
});
context('banned slur', () => {
beforeEach(() => {
sandbox.spy(email, 'sendTxn');
sandbox.stub(IncomingWebhook.prototype, 'send');
});
afterEach(() => {
sandbox.restore();
});
it('errors and revokes privileges when chat message contains a banned slur', async () => {
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: testSlurMessage})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('bannedSlurUsed'),
});
// Email sent to mods
await sleep(0.5);
expect(email.sendTxn).to.be.calledOnce;
expect(email.sendTxn.args[0][1]).to.be.eql('slur-report-to-mods');
// Slack message to mods
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
/* eslint-disable camelcase */
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: `${user.profile.name} (${user.id}) tried to post a slur`,
attachments: [{
fallback: 'Slur Message',
color: 'danger',
author_name: `${user.profile.name} - ${user.auth.local.email} - ${user._id}`,
title: 'Slur in Test Guild',
title_link: `${BASE_URL}/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});
});
});
it('does not error when sending a message to a private guild with a user with revoked chat', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {
@@ -364,24 +208,6 @@ describe('POST /chat', () => {
expect(message.message.id).to.exist;
});
it('adds backer info to chat', async () => {
const backerInfo = {
npc: 'Town Crier',
tier: 800,
tokensApplied: true,
};
const backer = await generateUser({
backer: backerInfo,
});
const message = await backer.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
const messageBackerInfo = message.message.backer;
expect(messageBackerInfo.npc).to.equal(backerInfo.npc);
expect(messageBackerInfo.tier).to.equal(backerInfo.tier);
expect(messageBackerInfo.tokensApplied).to.equal(backerInfo.tokensApplied);
});
it('sends group chat received webhooks', async () => {
let userUuid = generateUUID();
let memberUuid = generateUUID();
@@ -426,9 +252,6 @@ describe('POST /chat', () => {
expect(message.message.id).to.exist;
expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.exist;
expect(memberWithNotification.notifications.find(n => {
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupWithChat._id;
})).to.exist;
});
it('notifies other users of new messages for a party', async () => {
@@ -446,9 +269,6 @@ describe('POST /chat', () => {
expect(message.message.id).to.exist;
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
expect(memberWithNotification.notifications.find(n => {
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === group._id;
})).to.exist;
});
context('Spam prevention', () => {
@@ -1,6 +1,5 @@
import {
createAndPopulateGroup,
sleep,
} from '../../../../helpers/api-v3-integration.helper';
describe('POST /groups/:id/chat/seen', () => {
@@ -25,15 +24,10 @@ describe('POST /groups/:id/chat/seen', () => {
});
it('clears new messages for a guild', async () => {
await guildMember.sync();
const initialNotifications = guildMember.notifications.length;
await guildMember.post(`/groups/${guild._id}/chat/seen`);
await sleep(0.5);
let guildThatHasSeenChat = await guildMember.get('/user');
expect(guildThatHasSeenChat.notifications.length).to.equal(initialNotifications - 1);
expect(guildThatHasSeenChat.newMessages).to.be.empty;
});
});
@@ -59,13 +53,10 @@ describe('POST /groups/:id/chat/seen', () => {
});
it('clears new messages for a party', async () => {
await partyMember.sync();
const initialNotifications = partyMember.notifications.length;
await partyMember.post(`/groups/${party._id}/chat/seen`);
let partyMemberThatHasSeenChat = await partyMember.get('/user');
expect(partyMemberThatHasSeenChat.notifications.length).to.equal(initialNotifications - 1);
expect(partyMemberThatHasSeenChat.newMessages).to.be.empty;
});
});
@@ -3,7 +3,6 @@ import {
generateUser,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import config from '../../../../../config.json';
import { v4 as generateUUID } from 'uuid';
describe('POST /groups/:id/chat/:id/clearflags', () => {
@@ -75,7 +74,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
expect(messages[0].flagCount).to.eql(0);
});
it('can\'t flag a system message', async () => {
it('can unflag a system message', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {
type: 'party',
@@ -96,15 +95,13 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
await member.post('/user/class/cast/mpheal');
let [skillMsg] = await member.get(`/groups/${group.id}/chat`);
await expect(member.post(`/groups/${group._id}/chat/${skillMsg.id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('messageCannotFlagSystemMessages', {communityManagerEmail: config.EMAILS.COMMUNITY_MANAGER_EMAIL}),
});
// let messages = await members[0].get(`/groups/${group._id}/chat`);
// expect(messages[0].id).to.eql(skillMsg.id);
// expect(messages[0].flagCount).to.eql(0);
await member.post(`/groups/${group._id}/chat/${skillMsg.id}/flag`);
await admin.post(`/groups/${group._id}/chat/${skillMsg.id}/clearflags`);
let messages = await members[0].get(`/groups/${group._id}/chat`);
expect(messages[0].id).to.eql(skillMsg.id);
expect(messages[0].flagCount).to.eql(0);
});
});
@@ -1,8 +1,8 @@
import {
generateUser,
translate as t,
resetHabiticaDB,
} from '../../../../helpers/api-v3-integration.helper';
import apiMessages from '../../../../../website/server/libs/apiMessages';
describe('GET /coupons/', () => {
let user;
@@ -19,7 +19,7 @@ describe('GET /coupons/', () => {
await expect(user.get('/coupons')).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: apiMessages('noSudoAccess'),
message: t('noSudoAccess'),
});
});
@@ -4,7 +4,6 @@ import {
resetHabiticaDB,
} from '../../../../helpers/api-v3-integration.helper';
import couponCode from 'coupon-code';
import apiMessages from '../../../../../website/server/libs/apiMessages';
describe('POST /coupons/generate/:event', () => {
let user;
@@ -26,7 +25,7 @@ describe('POST /coupons/generate/:event', () => {
await expect(user.post('/coupons/generate/aaa')).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: apiMessages('noSudoAccess'),
message: t('noSudoAccess'),
});
});
@@ -4,7 +4,7 @@ import {
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
xdescribe('GET /export/avatar-:memberId.html', () => {
describe('GET /export/avatar-:memberId.html', () => {
let user;
before(async () => {
@@ -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);
});
});
@@ -16,12 +16,6 @@ describe('GET /groups', () => {
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5;
const GUILD_PER_PAGE = 30;
let categories = [{
slug: 'newCat',
name: 'New Category',
}];
let publicGuildNotMember;
let privateGuildUserIsMemberOf;
before(async () => {
await resetHabiticaDB();
@@ -37,18 +31,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,50 +100,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'))
@@ -201,8 +149,8 @@ describe('GET /groups', () => {
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=1'))
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
let page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2'))
.to.eventually.have.a.lengthOf(1 + 3); // 1 created now, 3 by other tests
expect(page2[3].name).to.equal('guild with less members');
.to.eventually.have.a.lengthOf(1 + 2); // 1 created now, 2 by other tests
expect(page2[2].name).to.equal('guild with less members');
});
});
@@ -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', 'loginIncentives',
]);
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 () => {
@@ -93,7 +79,7 @@ describe('GET /groups/:groupId/members', () => {
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', 'loginIncentives',
'backer', 'contributor', 'auth', 'items', 'inbox',
]);
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql([
@@ -161,19 +147,4 @@ describe('GET /groups/:groupId/members', () => {
let resIds = res.concat(res2).map(member => member._id);
expect(resIds).to.eql(expectedIds.sort());
});
it('searches members', async () => {
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
let usersToGenerate = [];
for (let i = 0; i < 2; i++) {
usersToGenerate.push(generateUser({party: {_id: group._id}}));
}
const usersCreated = await Promise.all(usersToGenerate);
const userToSearch = usersCreated[0].profile.name;
let res = await user.get(`/groups/party/members?search=${userToSearch}`);
expect(res.length).to.equal(1);
expect(res[0].profile.name).to.equal(userToSearch);
});
});
@@ -220,7 +220,7 @@ describe('POST /group/:groupId/join', () => {
it('clears invitation from user when joining party', async () => {
await invitedUser.post(`/groups/${party._id}/join`);
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.parties[0].id');
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id');
});
it('increments memberCount when joining party', async () => {
@@ -10,8 +10,6 @@ import { v4 as generateUUID } from 'uuid';
import {
each,
} from 'lodash';
import { model as User } from '../../../../../website/server/models/user';
import * as payments from '../../../../../website/server/libs/payments';
describe('POST /groups/:groupId/leave', () => {
let typesOfGroups = {
@@ -70,21 +68,13 @@ describe('POST /groups/:groupId/leave', () => {
it('removes new messages for that group from user', async () => {
await member.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
await sleep(0.5);
await leader.sync();
expect(leader.notifications.find(n => {
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id;
})).to.exist;
expect(leader.newMessages[groupToLeave._id]).to.not.be.empty;
await leader.post(`/groups/${groupToLeave._id}/leave`);
await leader.sync();
expect(leader.notifications.find(n => {
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id;
})).to.not.exist;
expect(leader.newMessages[groupToLeave._id]).to.be.empty;
});
@@ -257,7 +247,7 @@ describe('POST /groups/:groupId/leave', () => {
let userWithoutInvitation = await invitedUser.get('/user');
expect(userWithoutInvitation.invitations.parties[0]).to.be.empty;
expect(userWithoutInvitation.invitations.party).to.be.empty;
});
});
@@ -274,45 +264,4 @@ describe('POST /groups/:groupId/leave', () => {
expect(userWithNonExistentParty.party).to.eql({});
});
});
context('Leaving a group plan', () => {
it('cancels the free subscription', async () => {
// Create group
let { group, groupLeader, members } = await createAndPopulateGroup({
groupDetails: {
name: 'Test Private Guild',
type: 'guild',
},
members: 1,
});
let leader = groupLeader;
let member = members[0];
let userWithFreePlan = await User.findById(leader._id).exec();
// Create subscription
let paymentData = {
user: userWithFreePlan,
groupId: group._id,
sub: {
key: 'basic_3mo',
},
customerId: 'customer-id',
paymentMethod: 'Payment Method',
headers: {
'x-client': 'habitica-web',
'user-agent': '',
},
};
await payments.createSubscription(paymentData);
await member.sync();
expect(member.purchased.plan.planId).to.equal('group_plan_auto');
expect(member.purchased.plan.dateTerminated).to.not.exist;
// Leave
await member.post(`/groups/${group._id}/leave`);
await member.sync();
expect(member.purchased.plan.dateTerminated).to.exist;
});
});
});
@@ -107,7 +107,7 @@ describe('POST /group/:groupId/reject-invite', () => {
it('clears invitation from user', async () => {
await invitedUser.post(`/groups/${party._id}/reject-invite`);
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.parties[0].id');
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id');
});
});
});
@@ -2,7 +2,6 @@ import {
generateUser,
createAndPopulateGroup,
translate as t,
sleep,
} from '../../../../helpers/api-v3-integration.helper';
import * as email from '../../../../../website/server/libs/email';
@@ -178,31 +177,24 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
});
it('can remove other invites', async () => {
expect(partyInvitedUser.invitations.parties[0]).to.not.be.empty;
expect(partyInvitedUser.invitations.party).to.not.be.empty;
await partyLeader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`);
let invitedUserWithoutInvite = await partyInvitedUser.get('/user');
expect(invitedUserWithoutInvite.invitations.parties[0]).to.be.empty;
expect(invitedUserWithoutInvite.invitations.party).to.be.empty;
});
it('removes new messages from a member who is removed', async () => {
await partyLeader.post(`/groups/${party._id}/chat`, { message: 'Some message' });
await sleep(0.5);
await removedMember.sync();
expect(removedMember.notifications.find(n => {
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === party._id;
})).to.exist;
expect(removedMember.newMessages[party._id]).to.not.be.empty;
await partyLeader.post(`/groups/${party._id}/removeMember/${removedMember._id}`);
await removedMember.sync();
expect(removedMember.notifications.find(n => {
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === party._id;
})).to.not.exist;
expect(removedMember.newMessages[party._id]).to.be.empty;
});
@@ -110,7 +110,6 @@ describe('Post /groups/:groupId/invite', () => {
id: group._id,
name: groupName,
inviter: inviter._id,
publicGuild: false,
}]);
await expect(userToInvite.get('/user'))
@@ -128,13 +127,11 @@ describe('Post /groups/:groupId/invite', () => {
id: group._id,
name: groupName,
inviter: inviter._id,
publicGuild: false,
},
{
id: group._id,
name: groupName,
inviter: inviter._id,
publicGuild: false,
},
]);
@@ -443,38 +440,7 @@ describe('Post /groups/:groupId/invite', () => {
await inviter.post(`/groups/${party._id}/invite`, {
uuids: [userToInvite._id],
});
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
});
it('allow inviting a user to 2 different parties', async () => {
// Create another inviter
let inviter2 = await generateUser();
// Create user to invite
let userToInvite = await generateUser();
// Create second group
let party2 = await inviter2.post('/groups', {
name: 'Test Party 2',
type: 'party',
});
// Invite to first party
await inviter.post(`/groups/${party._id}/invite`, {
uuids: [userToInvite._id],
});
// Invite to second party
await inviter2.post(`/groups/${party2._id}/invite`, {
uuids: [userToInvite._id],
});
// Get updated user
let invitedUser = await userToInvite.get('/user');
expect(invitedUser.invitations.parties.length).to.equal(2);
expect(invitedUser.invitations.parties[0].id).to.equal(party._id);
expect(invitedUser.invitations.parties[1].id).to.equal(party2._id);
expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id);
});
it('allow inviting a user if party id is not associated with a real party', async () => {
@@ -485,7 +451,7 @@ describe('Post /groups/:groupId/invite', () => {
await inviter.post(`/groups/${party._id}/invite`, {
uuids: [userToInvite._id],
});
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id);
});
it('allows 30 members in a party', async () => {
@@ -45,20 +45,6 @@ describe('PUT /group', () => {
expect(updatedGroup.name).to.equal(groupUpdatedName);
});
it('updates a group categories', async () => {
let categories = [{
slug: 'newCat',
name: 'New Category',
}];
let updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, {
categories,
});
expect(updatedGroup.categories[0].slug).to.eql(categories[0].slug);
expect(updatedGroup.categories[0].name).to.eql(categories[0].name);
});
it('allows an admin to update a guild', async () => {
let updatedGroup = await adminUser.put(`/groups/${groupToUpdate._id}`, {
name: groupUpdatedName,
@@ -32,7 +32,7 @@ describe('GET /members/:memberId', () => {
let memberRes = await user.get(`/members/${member._id}`);
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', 'loginIncentives',
'backer', 'contributor', 'auth', 'items', 'inbox',
]);
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql([
@@ -98,7 +98,6 @@ describe('POST /members/send-private-message', () => {
it('sends a private message to a user', async () => {
let receiver = await generateUser();
// const initialNotifications = receiver.notifications.length;
await userToSendMessage.post('/members/send-private-message', {
message: messageToSend,
@@ -116,92 +115,6 @@ describe('POST /members/send-private-message', () => {
return message.uuid === receiver._id && message.text === messageToSend;
});
// @TODO waiting for mobile support
// expect(updatedReceiver.notifications.length).to.equal(initialNotifications + 1);
// const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1];
// expect(notification.type).to.equal('NEW_INBOX_MESSAGE');
// expect(notification.data.messageId).to.equal(sendersMessageInReceiversInbox.id);
// expect(notification.data.excerpt).to.equal(messageToSend);
// expect(notification.data.sender.id).to.equal(updatedSender._id);
// expect(notification.data.sender.name).to.equal(updatedSender.profile.name);
expect(sendersMessageInReceiversInbox).to.exist;
expect(sendersMessageInSendersInbox).to.exist;
});
// @TODO waiting for mobile support
xit('creates a notification with an excerpt if the message is too long', async () => {
let receiver = await generateUser();
let longerMessageToSend = 'A very long message, that for sure exceeds the limit of 100 chars for the excerpt that we set to 100 chars';
let messageExcerpt = `${longerMessageToSend.substring(0, 100)}...`;
await userToSendMessage.post('/members/send-private-message', {
message: longerMessageToSend,
toUserId: receiver._id,
});
let updatedReceiver = await receiver.get('/user');
let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (message) => {
return message.uuid === userToSendMessage._id && message.text === longerMessageToSend;
});
const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1];
expect(notification.type).to.equal('NEW_INBOX_MESSAGE');
expect(notification.data.messageId).to.equal(sendersMessageInReceiversInbox.id);
expect(notification.data.excerpt).to.equal(messageExcerpt);
});
it('allows admin to send when sender has blocked the admin', async () => {
userToSendMessage = await generateUser({
'contributor.admin': 1,
});
const receiver = await generateUser({'inbox.blocks': [userToSendMessage._id]});
await userToSendMessage.post('/members/send-private-message', {
message: messageToSend,
toUserId: receiver._id,
});
const updatedReceiver = await receiver.get('/user');
const updatedSender = await userToSendMessage.get('/user');
const sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (message) => {
return message.uuid === userToSendMessage._id && message.text === messageToSend;
});
const sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (message) => {
return message.uuid === receiver._id && message.text === messageToSend;
});
expect(sendersMessageInReceiversInbox).to.exist;
expect(sendersMessageInSendersInbox).to.exist;
});
it('allows admin to send when to user has opted out of messaging', async () => {
userToSendMessage = await generateUser({
'contributor.admin': 1,
});
const receiver = await generateUser({'inbox.optOut': true});
await userToSendMessage.post('/members/send-private-message', {
message: messageToSend,
toUserId: receiver._id,
});
const updatedReceiver = await receiver.get('/user');
const updatedSender = await userToSendMessage.get('/user');
const sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (message) => {
return message.uuid === userToSendMessage._id && message.text === messageToSend;
});
const sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (message) => {
return message.uuid === receiver._id && message.text === messageToSend;
});
expect(sendersMessageInReceiversInbox).to.exist;
expect(sendersMessageInSendersInbox).to.exist;
});
@@ -1,16 +0,0 @@
import {
requester,
} from '../../../../helpers/api-v3-integration.helper';
describe('GET /news', () => {
let api;
beforeEach(async () => {
api = requester();
});
it('returns the latest news in html format, does not require authentication', async () => {
const res = await api.get('/news');
expect(res).to.be.a.string;
});
});
@@ -1,42 +0,0 @@
import {
generateUser,
} from '../../../../helpers/api-v3-integration.helper';
describe('POST /news/tell-me-later', () => {
let user;
beforeEach(async () => {
user = await generateUser({
'flags.newStuff': true,
});
});
it('marks new stuff as read and adds notification', async () => {
expect(user.flags.newStuff).to.equal(true);
const initialNotifications = user.notifications.length;
await user.post('/news/tell-me-later');
await user.sync();
expect(user.flags.newStuff).to.equal(false);
expect(user.notifications.length).to.equal(initialNotifications + 1);
const notification = user.notifications[user.notifications.length - 1];
expect(notification.type).to.equal('NEW_STUFF');
// should be marked as seen by default so it's not counted in the number of notifications
expect(notification.seen).to.equal(true);
expect(notification.data.title).to.be.a.string;
});
it('never adds two notifications', async () => {
const initialNotifications = user.notifications.length;
await user.post('/news/tell-me-later');
await user.post('/news/tell-me-later');
await user.sync();
expect(user.notifications.length).to.equal(initialNotifications + 1);
});
});
@@ -13,45 +13,14 @@ describe('POST /notifications/:notificationId/read', () => {
it('errors when notification is not found', async () => {
let dummyId = generateUUID();
await expect(user.post(`/notifications/${dummyId}/read`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageNotificationNotFound'),
});
await expect(user.post(`/notifications/${dummyId}/read`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageNotificationNotFound'),
});
});
it('removes a notification', async () => {
expect(user.notifications.length).to.equal(0);
const id = generateUUID();
const id2 = generateUUID();
await user.update({
notifications: [{
id,
type: 'DROPS_ENABLED',
data: {},
}, {
id: id2,
type: 'LOGIN_INCENTIVE',
data: {},
}],
});
await user.sync();
expect(user.notifications.length).to.equal(2);
const res = await user.post(`/notifications/${id}/read`);
expect(res).to.deep.equal([{
id: id2,
type: 'LOGIN_INCENTIVE',
data: {},
seen: false,
}]);
await user.sync();
expect(user.notifications.length).to.equal(1);
expect(user.notifications[0].id).to.equal(id2);
xit('removes a notification', async () => {
});
});
@@ -1,59 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('POST /notifications/:notificationId/see', () => {
let user;
before(async () => {
user = await generateUser();
});
it('errors when notification is not found', async () => {
let dummyId = generateUUID();
await expect(user.post(`/notifications/${dummyId}/see`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageNotificationNotFound'),
});
});
it('mark a notification as seen', async () => {
expect(user.notifications.length).to.equal(0);
const id = generateUUID();
const id2 = generateUUID();
await user.update({
notifications: [{
id,
type: 'DROPS_ENABLED',
data: {},
}, {
id: id2,
type: 'LOGIN_INCENTIVE',
data: {},
}],
});
const userObj = await user.get('/user'); // so we can check that defaults have been applied
expect(userObj.notifications.length).to.equal(2);
expect(userObj.notifications[0].seen).to.equal(false);
const res = await user.post(`/notifications/${id}/see`);
expect(res).to.deep.equal({
id,
type: 'DROPS_ENABLED',
data: {},
seen: true,
});
await user.sync();
expect(user.notifications.length).to.equal(2);
expect(user.notifications[0].id).to.equal(id);
expect(user.notifications[0].seen).to.equal(true);
});
});
@@ -1,67 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('POST /notifications/read', () => {
let user;
before(async () => {
user = await generateUser();
});
it('errors when notification is not found', async () => {
let dummyId = generateUUID();
await expect(user.post('/notifications/read', {
notificationIds: [dummyId],
})).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageNotificationNotFound'),
});
});
it('removes multiple notifications', async () => {
expect(user.notifications.length).to.equal(0);
const id = generateUUID();
const id2 = generateUUID();
const id3 = generateUUID();
await user.update({
notifications: [{
id,
type: 'DROPS_ENABLED',
data: {},
}, {
id: id2,
type: 'LOGIN_INCENTIVE',
data: {},
}, {
id: id3,
type: 'CRON',
data: {},
}],
});
await user.sync();
expect(user.notifications.length).to.equal(3);
const res = await user.post('/notifications/read', {
notificationIds: [id, id3],
});
expect(res).to.deep.equal([{
id: id2,
type: 'LOGIN_INCENTIVE',
data: {},
seen: false,
}]);
await user.sync();
expect(user.notifications.length).to.equal(1);
expect(user.notifications[0].id).to.equal(id2);
});
});
@@ -1,88 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('POST /notifications/see', () => {
let user;
before(async () => {
user = await generateUser();
});
it('errors when notification is not found', async () => {
let dummyId = generateUUID();
await expect(user.post('/notifications/see', {
notificationIds: [dummyId],
})).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageNotificationNotFound'),
});
});
it('mark multiple notifications as seen', async () => {
expect(user.notifications.length).to.equal(0);
const id = generateUUID();
const id2 = generateUUID();
const id3 = generateUUID();
await user.update({
notifications: [{
id,
type: 'DROPS_ENABLED',
data: {},
seen: false,
}, {
id: id2,
type: 'LOGIN_INCENTIVE',
data: {},
seen: false,
}, {
id: id3,
type: 'CRON',
data: {},
seen: false,
}],
});
await user.sync();
expect(user.notifications.length).to.equal(3);
const res = await user.post('/notifications/see', {
notificationIds: [id, id3],
});
expect(res).to.deep.equal([
{
id,
type: 'DROPS_ENABLED',
data: {},
seen: true,
}, {
id: id2,
type: 'LOGIN_INCENTIVE',
data: {},
seen: false,
}, {
id: id3,
type: 'CRON',
data: {},
seen: true,
}]);
await user.sync();
expect(user.notifications.length).to.equal(3);
expect(user.notifications[0].id).to.equal(id);
expect(user.notifications[0].seen).to.equal(true);
expect(user.notifications[1].id).to.equal(id2);
expect(user.notifications[1].seen).to.equal(false);
expect(user.notifications[2].id).to.equal(id3);
expect(user.notifications[2].seen).to.equal(true);
});
});
@@ -6,7 +6,7 @@ import superagent from 'superagent';
import nconf from 'nconf';
const API_TEST_SERVER_PORT = nconf.get('PORT');
xdescribe('GET /qr-code/user/:memberId', () => {
describe('GET /qr-code/user/:memberId', () => {
let user;
before(async () => {
@@ -141,16 +141,6 @@ describe('DELETE /tasks/:id', () => {
});
});
it('removes a task from user.tasksOrder', async () => {
let task = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
});
await user.del(`/tasks/${task._id}`);
await user.sync();
expect(user.tasksOrder.habits.indexOf(task._id)).to.eql(-1);
});
it('removes a task from user.tasksOrder'); // TODO
});
});
@@ -1,4 +1,3 @@
import moment from 'moment';
import {
generateUser,
} from '../../../../helpers/api-integration/v3';
@@ -13,7 +12,7 @@ describe('GET /tasks/user', () => {
it('returns all user\'s tasks', async () => {
let createdTasks = await user.post('/tasks/user', [{text: 'test habit', type: 'habit'}, {text: 'test todo', type: 'todo'}]);
let tasks = await user.get('/tasks/user');
expect(tasks.length).to.equal(createdTasks.length + 1); // Plus one for generated todo
expect(tasks.length).to.equal(createdTasks.length + 1); // + 1 because 1 is a default task
});
it('returns only a type of user\'s tasks if req.query.type is specified', async () => {
@@ -128,106 +127,4 @@ describe('GET /tasks/user', () => {
let allCompletedTodos = await user.get('/tasks/user?type=_allCompletedTodos');
expect(allCompletedTodos.length).to.equal(numberOfTodos);
});
it('returns dailies with isDue for the date specified', async () => {
// @TODO Add required format
let startDate = moment().subtract('1', 'days').toISOString();
let createdTasks = await user.post('/tasks/user', [
{
text: 'test daily',
type: 'daily',
startDate,
frequency: 'daily',
everyX: 2,
},
]);
let dailys = await user.get('/tasks/user?type=dailys');
expect(dailys.length).to.be.at.least(1);
expect(dailys[0]._id).to.equal(createdTasks._id);
expect(dailys[0].isDue).to.be.false;
let dailys2 = await user.get(`/tasks/user?type=dailys&dueDate=${startDate}`);
expect(dailys2[0]._id).to.equal(createdTasks._id);
expect(dailys2[0].isDue).to.be.true;
});
xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
let timezone = 420;
await user.update({
'preferences.dayStart': 0,
'preferences.timezoneOffset': timezone,
});
let startDate = moment().zone(timezone).subtract('4', 'days').startOf('day').toISOString();
await user.post('/tasks/user', [
{
text: 'test daily',
type: 'daily',
startDate,
frequency: 'daily',
everyX: 2,
},
]);
let today = moment().format('YYYY-MM-DD');
let dailys = await user.get(`/tasks/user?type=dailys&dueDate=${today}`);
expect(dailys[0].isDue).to.be.true;
let yesterday = moment().subtract('1', 'days').format('YYYY-MM-DD');
let dailys2 = await user.get(`/tasks/user?type=dailys&dueDate=${yesterday}`);
expect(dailys2[0].isDue).to.be.false;
});
xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
let timezone = 240;
await user.update({
'preferences.dayStart': 0,
'preferences.timezoneOffset': timezone,
});
let startDate = moment().zone(timezone).subtract('4', 'days').startOf('day').toISOString();
await user.post('/tasks/user', [
{
text: 'test daily',
type: 'daily',
startDate,
frequency: 'daily',
everyX: 2,
},
]);
let today = moment().format('YYYY-MM-DD');
let dailys = await user.get(`/tasks/user?type=dailys&dueDate=${today}`);
expect(dailys[0].isDue).to.be.true;
let yesterday = moment().subtract('1', 'days').format('YYYY-MM-DD');
let dailys2 = await user.get(`/tasks/user?type=dailys&dueDate=${yesterday}`);
expect(dailys2[0].isDue).to.be.false;
});
xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
let timezone = 540;
await user.update({
'preferences.dayStart': 0,
'preferences.timezoneOffset': timezone,
});
let startDate = moment().zone(timezone).subtract('4', 'days').startOf('day').toISOString();
await user.post('/tasks/user', [
{
text: 'test daily',
type: 'daily',
startDate,
frequency: 'daily',
everyX: 2,
},
]);
let today = moment().format('YYYY-MM-DD');
let dailys = await user.get(`/tasks/user?type=dailys&dueDate=${today}`);
expect(dailys[0].isDue).to.be.true;
let yesterday = moment().subtract('1', 'days').format('YYYY-MM-DD');
let dailys2 = await user.get(`/tasks/user?type=dailys&dueDate=${yesterday}`);
expect(dailys2[0].isDue).to.be.false;
});
});

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