mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
Merge branch 'api-v3' of github.com:HabitRPG/habitrpg into api-v3
This commit is contained in:
@@ -28,7 +28,6 @@
|
|||||||
"no-new": 2,
|
"no-new": 2,
|
||||||
"no-octal-escape": 2,
|
"no-octal-escape": 2,
|
||||||
"no-octal": 2,
|
"no-octal": 2,
|
||||||
"no-param-reassign": 2,
|
|
||||||
"no-process-env": 2,
|
"no-process-env": 2,
|
||||||
"no-proto": 2,
|
"no-proto": 2,
|
||||||
"no-implied-eval": 2,
|
"no-implied-eval": 2,
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ module.exports = function(grunt) {
|
|||||||
|
|
||||||
grunt.registerTask('test:prepare:translations', function() {
|
grunt.registerTask('test:prepare:translations', function() {
|
||||||
require('babel/register');
|
require('babel/register');
|
||||||
var i18n = require('./website/src/libs/i18n'),
|
var i18n = require('./website/src/libs/api-v3/i18n'),
|
||||||
fs = require('fs');
|
fs = require('fs');
|
||||||
fs.writeFileSync('test/spec/mocks/translations.js',
|
fs.writeFileSync('test/spec/mocks/translations.js',
|
||||||
"if(!window.env) window.env = {};\n" +
|
"if(!window.env) window.env = {};\n" +
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
"babel-core": "^5.8.34",
|
"babel-core": "^5.8.34",
|
||||||
"babelify": "^6.x.x",
|
"babelify": "^6.x.x",
|
||||||
"body-parser": "^1.14.1",
|
"body-parser": "^1.14.1",
|
||||||
"compression": "^1.6.0",
|
|
||||||
"bower": "~1.3.12",
|
"bower": "~1.3.12",
|
||||||
"browserify": "~12.0.1",
|
"browserify": "~12.0.1",
|
||||||
|
"compression": "^1.6.0",
|
||||||
"connect-ratelimit": "0.0.7",
|
"connect-ratelimit": "0.0.7",
|
||||||
"cookie-parser": "^1.4.0",
|
"cookie-parser": "^1.4.0",
|
||||||
"cookie-session": "^1.2.0",
|
"cookie-session": "^1.2.0",
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
"nconf": "~0.8.2",
|
"nconf": "~0.8.2",
|
||||||
"newrelic": "~1.23.0",
|
"newrelic": "~1.23.0",
|
||||||
"nib": "~1.0.1",
|
"nib": "~1.0.1",
|
||||||
"nodemailer": "~0.5.2",
|
"nodemailer": "^1.9.0",
|
||||||
"pageres": "^1.0.1",
|
"pageres": "^1.0.1",
|
||||||
"passport": "~0.2.1",
|
"passport": "~0.2.1",
|
||||||
"passport-facebook": "2.0.0",
|
"passport-facebook": "2.0.0",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import mongoose from 'mongoose';
|
|||||||
import autoinc from 'mongoose-id-autoinc';
|
import autoinc from 'mongoose-id-autoinc';
|
||||||
import logger from '../website/src/libs/api-v3/logger';
|
import logger from '../website/src/libs/api-v3/logger';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import utils from '../website/src/libs/utils';
|
|
||||||
import repl from 'repl';
|
import repl from 'repl';
|
||||||
import gulp from 'gulp';
|
import gulp from 'gulp';
|
||||||
|
|
||||||
@@ -19,8 +18,6 @@ let improveRepl = (context) => {
|
|||||||
process.stdout.write('\u001B[2J\u001B[0;0f');
|
process.stdout.write('\u001B[2J\u001B[0;0f');
|
||||||
}});
|
}});
|
||||||
|
|
||||||
utils.setupConfig();
|
|
||||||
|
|
||||||
context.Challenge = require('../website/src/models/challenge').model;
|
context.Challenge = require('../website/src/models/challenge').model;
|
||||||
context.Group = require('../website/src/models/group').model;
|
context.Group = require('../website/src/models/group').model;
|
||||||
context.User = require('../website/src/models/user').model;
|
context.User = require('../website/src/models/user').model;
|
||||||
|
|||||||
18
test/api/v3/unit/libs/buildManifest.test.js
Normal file
18
test/api/v3/unit/libs/buildManifest.test.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import {
|
||||||
|
getManifestFiles,
|
||||||
|
} from '../../../../../website/src/libs/api-v3/buildManifest';
|
||||||
|
|
||||||
|
describe('Build Manifest', () => {
|
||||||
|
describe('getManifestFiles', () => {
|
||||||
|
it('returns an html string', () => {
|
||||||
|
let htmlCode = getManifestFiles('app');
|
||||||
|
|
||||||
|
expect(htmlCode.startsWith('<script') || htmlCode.startsWith('<link')).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error in case the page does not exist', () => {
|
||||||
|
let getManifestFilesFn = () => { getManifestFiles('strange name here') };
|
||||||
|
expect(getManifestFilesFn).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
217
test/api/v3/unit/libs/email.test.js
Normal file
217
test/api/v3/unit/libs/email.test.js
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import request from 'request';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
import Q from 'q';
|
||||||
|
import logger from '../../../../../website/src/libs/api-v3/logger';
|
||||||
|
|
||||||
|
function getUser () {
|
||||||
|
return {
|
||||||
|
_id: 'random _id',
|
||||||
|
auth: {
|
||||||
|
local: {
|
||||||
|
username: 'username',
|
||||||
|
email: 'email@email',
|
||||||
|
},
|
||||||
|
facebook: {
|
||||||
|
emails: [{
|
||||||
|
value: 'email@facebook'
|
||||||
|
}],
|
||||||
|
displayName: 'fb display name',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
name: 'profile name',
|
||||||
|
},
|
||||||
|
preferences: {
|
||||||
|
emailNotifications: {
|
||||||
|
unsubscribeFromAll: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('emails', () => {
|
||||||
|
let pathToEmailLib = '../../../../../website/src/libs/api-v3/email';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
delete require.cache[require.resolve(pathToEmailLib)];
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sendEmail', () => {
|
||||||
|
it('can send an email using the default transport', () => {
|
||||||
|
let sendMailSpy = sandbox.stub().returns(Q.defer().promise);
|
||||||
|
|
||||||
|
sandbox.stub(nodemailer, 'createTransport').returns({
|
||||||
|
sendMail: sendMailSpy,
|
||||||
|
});
|
||||||
|
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
attachEmail.send();
|
||||||
|
expect(sendMailSpy).to.be.calledOnce;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('logs errors', (done) => {
|
||||||
|
let deferred = Q.defer();
|
||||||
|
let sendMailSpy = sandbox.stub().returns(deferred.promise);
|
||||||
|
|
||||||
|
sandbox.stub(nodemailer, 'createTransport').returns({
|
||||||
|
sendMail: sendMailSpy,
|
||||||
|
});
|
||||||
|
sandbox.stub(logger, 'error');
|
||||||
|
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
attachEmail.send();
|
||||||
|
expect(sendMailSpy).to.be.calledOnce;
|
||||||
|
deferred.reject();
|
||||||
|
deferred.promise.catch((err) => {
|
||||||
|
expect(logger.error).to.be.calledOnce;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getUserInfo', () => {
|
||||||
|
it('returns an empty object if no field request', () => {
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let getUserInfo = attachEmail.getUserInfo;
|
||||||
|
expect(getUserInfo({}, [])).to.be.empty;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct user data', () => {
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let getUserInfo = attachEmail.getUserInfo;
|
||||||
|
let user = getUser();
|
||||||
|
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||||
|
|
||||||
|
expect(data).to.have.property('name', user.profile.name);
|
||||||
|
expect(data).to.have.property('email', user.auth.local.email);
|
||||||
|
expect(data).to.have.property('_id', user._id);
|
||||||
|
expect(data).to.have.property('canSend', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct user data [facebook users]', () => {
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let getUserInfo = attachEmail.getUserInfo;
|
||||||
|
let user = getUser();
|
||||||
|
delete user.profile['name'];
|
||||||
|
delete user.auth['local'];
|
||||||
|
|
||||||
|
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||||
|
|
||||||
|
expect(data).to.have.property('name', user.auth.facebook.displayName);
|
||||||
|
expect(data).to.have.property('email', user.auth.facebook.emails[0].value);
|
||||||
|
expect(data).to.have.property('_id', user._id);
|
||||||
|
expect(data).to.have.property('canSend', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has fallbacks for missing data', () => {
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let getUserInfo = attachEmail.getUserInfo;
|
||||||
|
let user = getUser();
|
||||||
|
delete user.profile['name'];
|
||||||
|
delete user.auth.local['email']
|
||||||
|
delete user.auth['facebook'];
|
||||||
|
|
||||||
|
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||||
|
|
||||||
|
expect(data).to.have.property('name', user.auth.local.username);
|
||||||
|
expect(data).not.to.have.property('email');
|
||||||
|
expect(data).to.have.property('_id', user._id);
|
||||||
|
expect(data).to.have.property('canSend', true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sendTxnEmail', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox.stub(request, 'post');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can send a txn email to one recipient', () => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let sendTxnEmail = attachEmail.sendTxn;
|
||||||
|
let emailType = 'an email type';
|
||||||
|
let mailingInfo = {
|
||||||
|
name: 'my name',
|
||||||
|
email: 'my@email',
|
||||||
|
};
|
||||||
|
|
||||||
|
sendTxnEmail(mailingInfo, emailType);
|
||||||
|
expect(request.post).to.be.calledWith(sinon.match({
|
||||||
|
json: {
|
||||||
|
data: {
|
||||||
|
emailType: sinon.match.same(emailType),
|
||||||
|
to: sinon.match((value) => {
|
||||||
|
return Array.isArray(value) && value[0].name === mailingInfo.name;
|
||||||
|
}, 'matches mailing info array'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not send email if address is missing', () => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let sendTxnEmail = attachEmail.sendTxn;
|
||||||
|
let emailType = 'an email type';
|
||||||
|
let mailingInfo = {
|
||||||
|
name: 'my name',
|
||||||
|
//email: 'my@email',
|
||||||
|
};
|
||||||
|
|
||||||
|
sendTxnEmail(mailingInfo, emailType);
|
||||||
|
expect(request.post).not.to.be.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses getUserInfo in case of user data', () => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let sendTxnEmail = attachEmail.sendTxn;
|
||||||
|
let emailType = 'an email type';
|
||||||
|
let mailingInfo = getUser();
|
||||||
|
|
||||||
|
sendTxnEmail(mailingInfo, emailType);
|
||||||
|
expect(request.post).to.be.calledWith(sinon.match({
|
||||||
|
json: {
|
||||||
|
data: {
|
||||||
|
emailType: sinon.match.same(emailType),
|
||||||
|
to: sinon.match(val => val[0]._id === mailingInfo._id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends email with some default variables', () => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let sendTxnEmail = attachEmail.sendTxn;
|
||||||
|
let emailType = 'an email type';
|
||||||
|
let mailingInfo = {
|
||||||
|
name: 'my name',
|
||||||
|
email: 'my@email',
|
||||||
|
};
|
||||||
|
let variables = [1,2,3];
|
||||||
|
|
||||||
|
sendTxnEmail(mailingInfo, emailType, variables);
|
||||||
|
expect(request.post).to.be.calledWith(sinon.match({
|
||||||
|
json: {
|
||||||
|
data: {
|
||||||
|
variables: sinon.match((value) => {
|
||||||
|
return value[0].name === 'BASE_URL';
|
||||||
|
}, 'matches variables'),
|
||||||
|
personalVariables: sinon.match((value) => {
|
||||||
|
return (value[0].rcpt === mailingInfo.email
|
||||||
|
&& value[0].vars[0].name === 'RECIPIENT_NAME'
|
||||||
|
&& value[0].vars[1].name === 'RECIPIENT_UNSUB_URL'
|
||||||
|
);
|
||||||
|
}, 'matches personal variables'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
15
test/api/v3/unit/libs/encryption.test.js
Normal file
15
test/api/v3/unit/libs/encryption.test.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import {
|
||||||
|
encrypt,
|
||||||
|
decrypt,
|
||||||
|
} from '../../../../../website/src/libs/api-v3/encryption';
|
||||||
|
|
||||||
|
describe('encryption', () => {
|
||||||
|
it('can encrypt and decrypt', () => {
|
||||||
|
let data = 'some secret text';
|
||||||
|
let encrypted = encrypt(data);
|
||||||
|
let decrypted = decrypt(encrypted);
|
||||||
|
|
||||||
|
expect(encrypted).not.to.equal(data);
|
||||||
|
expect(data).to.equal(decrypted);
|
||||||
|
});
|
||||||
|
});
|
||||||
46
test/api/v3/unit/libs/i18n.test.js
Normal file
46
test/api/v3/unit/libs/i18n.test.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
translations,
|
||||||
|
localePath,
|
||||||
|
langCodes,
|
||||||
|
} from '../../../../../website/src/libs/api-v3/i18n';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
describe('i18n', () => {
|
||||||
|
let listOfLocales = [];
|
||||||
|
|
||||||
|
before((done) => {
|
||||||
|
fs.readdir(localePath, (err, files) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
if (fs.statSync(path.join(localePath, file)).isDirectory() === false) return;
|
||||||
|
listOfLocales.push(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
listOfLocales = listOfLocales.sort();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('translations', () => {
|
||||||
|
it('includes a translation object for each locale', () => {
|
||||||
|
listOfLocales.forEach((locale) => {
|
||||||
|
expect(translations[locale]).to.be.an('object');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('localePath', () => {
|
||||||
|
it('is an absolute path to common/locales/', () => {
|
||||||
|
expect(localePath).to.match(/.*\/common\/locales\//);
|
||||||
|
expect(localePath)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('langCodes', () => {
|
||||||
|
it('is a list of all the language codes', () => {
|
||||||
|
expect(langCodes.sort()).to.eql(listOfLocales);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
241
test/api/v3/unit/middlewares/getUserLanguage.test.js
Normal file
241
test/api/v3/unit/middlewares/getUserLanguage.test.js
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import {
|
||||||
|
generateRes,
|
||||||
|
generateReq,
|
||||||
|
generateNext,
|
||||||
|
} from '../../../../helpers/api-unit.helper';
|
||||||
|
import getUserLanguage from '../../../../../website/src/middlewares/api-v3/getUserLanguage';
|
||||||
|
import Q from 'q';
|
||||||
|
import { model as User } from '../../../../../website/src/models/user';
|
||||||
|
import { translations } from '../../../../../website/src/libs/api-v3/i18n';
|
||||||
|
import accepts from 'accepts';
|
||||||
|
|
||||||
|
describe('getUserLanguage', () => {
|
||||||
|
let res, req, next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
res = generateRes();
|
||||||
|
req = generateReq();
|
||||||
|
next = generateNext();
|
||||||
|
});
|
||||||
|
|
||||||
|
context('query parameter', () => {
|
||||||
|
it('uses the language in the query parameter if avalaible', () => {
|
||||||
|
req.query = {
|
||||||
|
lang: 'es',
|
||||||
|
};
|
||||||
|
|
||||||
|
getUserLanguage(req, res, next);
|
||||||
|
expect(req.language).to.equal('es');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to english if the query parameter language does not exists', () => {
|
||||||
|
req.query = {
|
||||||
|
lang: 'bla',
|
||||||
|
};
|
||||||
|
|
||||||
|
getUserLanguage(req, res, next);
|
||||||
|
expect(req.language).to.equal('en');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses query even if the request includes a user and session', () => {
|
||||||
|
req.query = {
|
||||||
|
lang: 'es',
|
||||||
|
};
|
||||||
|
|
||||||
|
req.locals = {
|
||||||
|
user: {
|
||||||
|
preferences: {
|
||||||
|
language: 'it',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
req.session = {
|
||||||
|
userId: 123
|
||||||
|
};
|
||||||
|
|
||||||
|
getUserLanguage(req, res, next);
|
||||||
|
expect(req.language).to.equal('es');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('authorized request', () => {
|
||||||
|
it('uses the user preferred language if avalaible', () => {
|
||||||
|
req.locals = {
|
||||||
|
user: {
|
||||||
|
preferences: {
|
||||||
|
language: 'it',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
getUserLanguage(req, res, next);
|
||||||
|
expect(req.language).to.equal('it');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to english if the user preferred language is not avalaible', (done) => {
|
||||||
|
req.locals = {
|
||||||
|
user: {
|
||||||
|
preferences: {
|
||||||
|
language: 'bla',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('en');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the user preferred language even if a session is included in request', () => {
|
||||||
|
req.locals = {
|
||||||
|
user: {
|
||||||
|
preferences: {
|
||||||
|
language: 'it',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
req.session = {
|
||||||
|
userId: 123
|
||||||
|
};
|
||||||
|
|
||||||
|
getUserLanguage(req, res, next);
|
||||||
|
expect(req.language).to.equal('it');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('request with session', () => {
|
||||||
|
it('uses the user preferred language if avalaible', (done) => {
|
||||||
|
sandbox.stub(User, 'findOne').returns({
|
||||||
|
exec() {
|
||||||
|
return Q.resolve({
|
||||||
|
preferences: {
|
||||||
|
language: 'it',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
req.session = {
|
||||||
|
userId: 123
|
||||||
|
};
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('it');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('browser fallback', () => {
|
||||||
|
it('uses browser specificed language', (done) => {
|
||||||
|
req.headers['accept-language'] = 'pt';
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('pt');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses first language in series if browser specifies multiple', (done) => {
|
||||||
|
req.headers['accept-language'] = 'he, pt, it';
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('he');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips invalid lanaguages and uses first language in series if browser specifies multiple', (done) => {
|
||||||
|
req.headers['accept-language'] = 'blah, he, pt, it';
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('he');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses normal version of language if specialized locale is passed in', (done) => {
|
||||||
|
req.headers['accept-language'] = 'fr-CA';
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('fr');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses normal version of language if specialized locale is passed in', (done) => {
|
||||||
|
req.headers['accept-language'] = 'fr-CA';
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('fr');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses es if es is passed in', (done) => {
|
||||||
|
req.headers['accept-language'] = 'es';
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('es');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses es_419 if applicable es-languages are passed in', (done) => {
|
||||||
|
req.headers['accept-language'] = 'es-mx';
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('es_419');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses es_419 if multiple es languages are passed in', (done) => {
|
||||||
|
req.headers['accept-language'] = 'es-GT, es-MX, es-CR';
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('es_419');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('zh', (done) => {
|
||||||
|
req.headers['accept-language'] = 'zh-TW';
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('zh_TW');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses english if browser specified language is not compatible', (done) => {
|
||||||
|
req.headers['accept-language'] = 'blah';
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('en');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses english if browser does not specify', (done) => {
|
||||||
|
req.headers['accept-language'] = '';
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('en');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses english if browser does not supply an accept-language header', (done) => {
|
||||||
|
delete req.headers['accept-language'];
|
||||||
|
|
||||||
|
getUserLanguage(req, res, () => {
|
||||||
|
expect(req.language).to.equal('en');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
925
test/common/algos.mocha.coffee
Normal file
925
test/common/algos.mocha.coffee
Normal file
@@ -0,0 +1,925 @@
|
|||||||
|
_ = require 'lodash'
|
||||||
|
expect = require 'expect.js'
|
||||||
|
sinon = require 'sinon'
|
||||||
|
moment = require 'moment'
|
||||||
|
shared = require '../../common/script/index.js'
|
||||||
|
shared.i18n.translations = require('../../website/src/libs/api-v2/i18n.js').translations
|
||||||
|
test_helper = require './test_helper'
|
||||||
|
test_helper.addCustomMatchers()
|
||||||
|
$w = (s)->s.split(' ')
|
||||||
|
|
||||||
|
### Helper Functions ####
|
||||||
|
newUser = (addTasks=true)->
|
||||||
|
buffs = {per:0, int:0, con:0, str:0, stealth: 0, streaks: false}
|
||||||
|
user =
|
||||||
|
auth:
|
||||||
|
timestamps: {}
|
||||||
|
stats: {str:1, con:1, per:1, int:1, mp: 32, class: 'warrior', buffs: buffs}
|
||||||
|
items:
|
||||||
|
lastDrop:
|
||||||
|
count: 0
|
||||||
|
hatchingPotions: {}
|
||||||
|
eggs: {}
|
||||||
|
food: {}
|
||||||
|
gear:
|
||||||
|
equipped: {}
|
||||||
|
costume: {}
|
||||||
|
owned: {}
|
||||||
|
quests: {}
|
||||||
|
party:
|
||||||
|
quest:
|
||||||
|
progress:
|
||||||
|
down: 0
|
||||||
|
preferences: {
|
||||||
|
autoEquip: true
|
||||||
|
}
|
||||||
|
dailys: []
|
||||||
|
todos: []
|
||||||
|
rewards: []
|
||||||
|
flags: {}
|
||||||
|
achievements:
|
||||||
|
ultimateGearSets: {}
|
||||||
|
contributor:
|
||||||
|
level: 2
|
||||||
|
_tmp: {}
|
||||||
|
|
||||||
|
shared.wrap(user)
|
||||||
|
user.ops.reset(null, ->)
|
||||||
|
if addTasks
|
||||||
|
_.each ['habit', 'todo', 'daily'], (task)->
|
||||||
|
user.ops.addTask {body: {type: task, id: shared.uuid()}}
|
||||||
|
user
|
||||||
|
|
||||||
|
rewrapUser = (user)->
|
||||||
|
user._wrapped = false
|
||||||
|
shared.wrap(user)
|
||||||
|
user
|
||||||
|
|
||||||
|
expectStrings = (obj, paths) ->
|
||||||
|
_.each paths, (path) -> expect(obj[path]).to.be.ok()
|
||||||
|
|
||||||
|
# options.daysAgo: days ago when the last cron was executed
|
||||||
|
# cronAfterStart: moves the lastCron to be after the dayStart.
|
||||||
|
# This way the daysAgo works as expected if the test case
|
||||||
|
# makes the assumption that the lastCron was after dayStart.
|
||||||
|
beforeAfter = (options={}) ->
|
||||||
|
user = newUser()
|
||||||
|
[before, after] = [user, _.cloneDeep(user)]
|
||||||
|
# avoid closure on the original user
|
||||||
|
rewrapUser(after)
|
||||||
|
before.preferences.dayStart = after.preferences.dayStart = options.dayStart if options.dayStart
|
||||||
|
before.preferences.timezoneOffset = after.preferences.timezoneOffset = (options.timezoneOffset or moment().zone())
|
||||||
|
if options.limitOne
|
||||||
|
before["#{options.limitOne}s"] = [before["#{options.limitOne}s"][0]]
|
||||||
|
after["#{options.limitOne}s"] = [after["#{options.limitOne}s"][0]]
|
||||||
|
lastCron = moment(options.now || +new Date).subtract( {days:options.daysAgo} ) if options.daysAgo
|
||||||
|
lastCron.add( {hours:options.dayStart, minutes:1} ) if options.daysAgo and options.cronAfterStart
|
||||||
|
lastCron = +lastCron if options.daysAgo
|
||||||
|
_.each [before,after], (obj) ->
|
||||||
|
obj.lastCron = lastCron if options.daysAgo
|
||||||
|
{before:before, after:after}
|
||||||
|
#TODO calculate actual points
|
||||||
|
|
||||||
|
expectLostPoints = (before, after, taskType) ->
|
||||||
|
if taskType in ['daily','habit']
|
||||||
|
expect(after.stats.hp).to.be.lessThan before.stats.hp
|
||||||
|
expect(after["#{taskType}s"][0].history).to.have.length(1)
|
||||||
|
else expect(after.history.todos).to.have.length(1)
|
||||||
|
expect(after).toHaveExp 0
|
||||||
|
expect(after).toHaveGP 0
|
||||||
|
expect(after["#{taskType}s"][0].value).to.be.lessThan before["#{taskType}s"][0].value
|
||||||
|
|
||||||
|
expectGainedPoints = (before, after, taskType) ->
|
||||||
|
expect(after.stats.hp).to.be 50
|
||||||
|
expect(after.stats.exp).to.be.greaterThan before.stats.exp
|
||||||
|
expect(after.stats.gp).to.be.greaterThan before.stats.gp
|
||||||
|
expect(after["#{taskType}s"][0].value).to.be.greaterThan before["#{taskType}s"][0].value
|
||||||
|
expect(after["#{taskType}s"][0].history).to.have.length(1) if taskType is 'habit'
|
||||||
|
# daily & todo histories handled on cron
|
||||||
|
|
||||||
|
expectNoChange = (before,after) ->
|
||||||
|
_.each $w('stats items gear dailys todos rewards preferences'), (attr)->
|
||||||
|
expect(after[attr]).to.eql before[attr]
|
||||||
|
|
||||||
|
expectClosePoints = (before, after, taskType) ->
|
||||||
|
expect( Math.abs(after.stats.exp - before.stats.exp) ).to.be.lessThan 0.0001
|
||||||
|
expect( Math.abs(after.stats.gp - before.stats.gp) ).to.be.lessThan 0.0001
|
||||||
|
expect( Math.abs(after["#{taskType}s"][0].value - before["#{taskType}s"][0].value) ).to.be.lessThan 0.0001
|
||||||
|
|
||||||
|
expectDayResetNoDamage = (b,a) ->
|
||||||
|
[before,after] = [_.cloneDeep(b), _.cloneDeep(a)]
|
||||||
|
_.each after.dailys, (task,i) ->
|
||||||
|
expect(task.completed).to.be false
|
||||||
|
expect(before.dailys[i].value).to.be task.value
|
||||||
|
expect(before.dailys[i].streak).to.be task.streak
|
||||||
|
expect(task.history).to.have.length(1)
|
||||||
|
_.each after.todos, (task,i) ->
|
||||||
|
expect(task.completed).to.be false
|
||||||
|
expect(before.todos[i].value).to.be.greaterThan task.value
|
||||||
|
expect(after.history.todos).to.have.length(1)
|
||||||
|
# hack so we can compare user before/after obj equality sans effected paths
|
||||||
|
_.each [before,after], (obj) ->
|
||||||
|
delete obj.stats.buffs
|
||||||
|
_.each $w('dailys todos history lastCron'), (path) -> delete obj[path]
|
||||||
|
delete after._tmp
|
||||||
|
expectNoChange(before, after)
|
||||||
|
|
||||||
|
cycle = (array)->
|
||||||
|
n = -1
|
||||||
|
(seed=0)->
|
||||||
|
n++
|
||||||
|
return array[n % array.length]
|
||||||
|
|
||||||
|
repeatWithoutLastWeekday = ()->
|
||||||
|
repeat = {su:true,m:true,t:true,w:true,th:true,f:true,s:true}
|
||||||
|
if shared.startOfWeek(moment().zone(0)).isoWeekday() == 1 # Monday
|
||||||
|
repeat.su = false
|
||||||
|
else
|
||||||
|
repeat.s = false
|
||||||
|
{repeat: repeat}
|
||||||
|
|
||||||
|
###### Specs ######
|
||||||
|
|
||||||
|
describe 'User', ->
|
||||||
|
it 'sets correct user defaults', ->
|
||||||
|
user = newUser()
|
||||||
|
base_gear = { armor: 'armor_base_0', weapon: 'weapon_base_0', head: 'head_base_0', shield: 'shield_base_0' }
|
||||||
|
buffs = {per:0, int:0, con:0, str:0, stealth: 0, streaks: false}
|
||||||
|
expect(user.stats).to.eql { str: 1, con: 1, per: 1, int: 1, hp: 50, mp: 32, lvl: 1, exp: 0, gp: 0, class: 'warrior', buffs: buffs }
|
||||||
|
expect(user.items.gear).to.eql { equipped: base_gear, costume: base_gear, owned: {weapon_warrior_0: true} }
|
||||||
|
expect(user.preferences).to.eql { autoEquip: true, costume: false }
|
||||||
|
|
||||||
|
it 'calculates max MP', ->
|
||||||
|
user = newUser()
|
||||||
|
expect(user).toHaveMaxMP 32
|
||||||
|
user.stats.int = 10
|
||||||
|
expect(user).toHaveMaxMP 50
|
||||||
|
user.stats.lvl = 5
|
||||||
|
expect(user).toHaveMaxMP 54
|
||||||
|
user.stats.class = 'wizard'
|
||||||
|
user.items.gear.equipped.weapon = 'weapon_wizard_1'
|
||||||
|
expect(user).toHaveMaxMP 63
|
||||||
|
|
||||||
|
it 'handles perfect days', ->
|
||||||
|
user = newUser()
|
||||||
|
user.dailys = []
|
||||||
|
_.times 3, ->user.dailys.push shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days')})
|
||||||
|
cron = -> user.lastCron = moment().subtract(1,'days');user.fns.cron()
|
||||||
|
|
||||||
|
cron()
|
||||||
|
expect(user.stats.buffs.str).to.be 0
|
||||||
|
expect(user.achievements.perfect).to.not.be.ok()
|
||||||
|
|
||||||
|
user.dailys[0].completed = true
|
||||||
|
cron()
|
||||||
|
expect(user.stats.buffs.str).to.be 0
|
||||||
|
expect(user.achievements.perfect).to.not.be.ok()
|
||||||
|
|
||||||
|
_.each user.dailys, (d)->d.completed = true
|
||||||
|
cron()
|
||||||
|
expect(user.stats.buffs.str).to.be 1
|
||||||
|
expect(user.achievements.perfect).to.be 1
|
||||||
|
|
||||||
|
# Handle greyed-out dailys
|
||||||
|
yesterday = moment().subtract(1,'days')
|
||||||
|
user.dailys[0].repeat[shared.dayMapping[yesterday.day()]] = false
|
||||||
|
_.each user.dailys[1..], (d)->d.completed = true
|
||||||
|
cron()
|
||||||
|
expect(user.stats.buffs.str).to.be 1
|
||||||
|
expect(user.achievements.perfect).to.be 2
|
||||||
|
|
||||||
|
describe 'Resting in the Inn', ->
|
||||||
|
user = null
|
||||||
|
cron = null
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
user = newUser()
|
||||||
|
user.preferences.sleep = true
|
||||||
|
cron = -> user.lastCron = moment().subtract(1, 'days');user.fns.cron()
|
||||||
|
user.dailys = []
|
||||||
|
_.times 2, -> user.dailys.push shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days')})
|
||||||
|
|
||||||
|
it 'remains in the inn on cron', ->
|
||||||
|
cron()
|
||||||
|
expect(user.preferences.sleep).to.be true
|
||||||
|
|
||||||
|
it 'resets dailies', ->
|
||||||
|
user.dailys[0].completed = true
|
||||||
|
cron()
|
||||||
|
expect(user.dailys[0].completed).to.be false
|
||||||
|
|
||||||
|
it 'resets checklist on incomplete dailies', ->
|
||||||
|
user.dailys[0].checklist = [
|
||||||
|
{
|
||||||
|
"text" : "1",
|
||||||
|
"id" : "checklist-one",
|
||||||
|
"completed" : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text" : "2",
|
||||||
|
"id" : "checklist-two",
|
||||||
|
"completed" : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text" : "3",
|
||||||
|
"id" : "checklist-three",
|
||||||
|
"completed" : false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
cron()
|
||||||
|
_.each user.dailys[0].checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
it 'resets checklist on complete dailies', ->
|
||||||
|
user.dailys[0].checklist = [
|
||||||
|
{
|
||||||
|
"text" : "1",
|
||||||
|
"id" : "checklist-one",
|
||||||
|
"completed" : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text" : "2",
|
||||||
|
"id" : "checklist-two",
|
||||||
|
"completed" : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text" : "3",
|
||||||
|
"id" : "checklist-three",
|
||||||
|
"completed" : false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
user.dailys[0].completed = true
|
||||||
|
cron()
|
||||||
|
_.each user.dailys[0].checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
it 'does not reset checklist on grey incomplete dailies', ->
|
||||||
|
yesterday = moment().subtract(1,'days')
|
||||||
|
user.dailys[0].repeat[shared.dayMapping[yesterday.day()]] = false
|
||||||
|
user.dailys[0].checklist = [
|
||||||
|
{
|
||||||
|
"text" : "1",
|
||||||
|
"id" : "checklist-one",
|
||||||
|
"completed" : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text" : "2",
|
||||||
|
"id" : "checklist-two",
|
||||||
|
"completed" : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text" : "3",
|
||||||
|
"id" : "checklist-three",
|
||||||
|
"completed" : true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
cron()
|
||||||
|
_.each user.dailys[0].checklist, (box)->
|
||||||
|
expect(box.completed).to.be true
|
||||||
|
|
||||||
|
it 'resets checklist on complete grey complete dailies', ->
|
||||||
|
yesterday = moment().subtract(1,'days')
|
||||||
|
user.dailys[0].repeat[shared.dayMapping[yesterday.day()]] = false
|
||||||
|
user.dailys[0].checklist = [
|
||||||
|
{
|
||||||
|
"text" : "1",
|
||||||
|
"id" : "checklist-one",
|
||||||
|
"completed" : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text" : "2",
|
||||||
|
"id" : "checklist-two",
|
||||||
|
"completed" : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text" : "3",
|
||||||
|
"id" : "checklist-three",
|
||||||
|
"completed" : true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
user.dailys[0].completed = true
|
||||||
|
|
||||||
|
cron()
|
||||||
|
_.each user.dailys[0].checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
it 'does not damage user for incomplete dailies', ->
|
||||||
|
expect(user).toHaveHP 50
|
||||||
|
user.dailys[0].completed = true
|
||||||
|
user.dailys[1].completed = false
|
||||||
|
cron()
|
||||||
|
expect(user).toHaveHP 50
|
||||||
|
|
||||||
|
it 'gives credit for complete dailies', ->
|
||||||
|
user.dailys[0].completed = true
|
||||||
|
expect(user.dailys[0].history).to.be.empty
|
||||||
|
cron()
|
||||||
|
expect(user.dailys[0].history).to.not.be.empty
|
||||||
|
|
||||||
|
it 'damages user for incomplete dailies after checkout', ->
|
||||||
|
expect(user).toHaveHP 50
|
||||||
|
user.dailys[0].completed = true
|
||||||
|
user.dailys[1].completed = false
|
||||||
|
user.preferences.sleep = false
|
||||||
|
cron()
|
||||||
|
expect(user.stats.hp).to.be.lessThan 50
|
||||||
|
|
||||||
|
describe 'Death', ->
|
||||||
|
user = undefined
|
||||||
|
it 'revives correctly', ->
|
||||||
|
user = newUser()
|
||||||
|
user.stats = { gp: 10, exp: 100, lvl: 2, hp: 0, class: 'warrior' }
|
||||||
|
user.ops.revive()
|
||||||
|
expect(user).toHaveGP 0
|
||||||
|
expect(user).toHaveExp 0
|
||||||
|
expect(user).toHaveLevel 1
|
||||||
|
expect(user).toHaveHP 50
|
||||||
|
expect(user.items.gear.owned).to.eql { weapon_warrior_0: false }
|
||||||
|
|
||||||
|
it "doesn't break unbreakables", ->
|
||||||
|
ce = shared.countExists
|
||||||
|
user = newUser()
|
||||||
|
# breakables (includes default weapon_warrior_0):
|
||||||
|
user.items.gear.owned['shield_warrior_1'] = true
|
||||||
|
# unbreakables because off-class or 0 value:
|
||||||
|
user.items.gear.owned['shield_rogue_1'] = true
|
||||||
|
user.items.gear.owned['head_special_nye'] = true
|
||||||
|
expect(ce user.items.gear.owned).to.be 4
|
||||||
|
user.stats.hp = 0
|
||||||
|
user.ops.revive()
|
||||||
|
expect(ce(user.items.gear.owned)).to.be 3
|
||||||
|
user.stats.hp = 0
|
||||||
|
user.ops.revive()
|
||||||
|
expect(ce(user.items.gear.owned)).to.be 2
|
||||||
|
user.stats.hp = 0
|
||||||
|
user.ops.revive()
|
||||||
|
expect(ce(user.items.gear.owned)).to.be 2
|
||||||
|
expect(user.items.gear.owned).to.eql { weapon_warrior_0: false, shield_warrior_1: false, shield_rogue_1: true, head_special_nye: true }
|
||||||
|
|
||||||
|
it "handles event items", ->
|
||||||
|
shared.content.gear.flat.head_special_nye.event.start = '2012-01-01'
|
||||||
|
shared.content.gear.flat.head_special_nye.event.end = '2012-02-01'
|
||||||
|
expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be true
|
||||||
|
delete user.items.gear.owned['head_special_nye']
|
||||||
|
expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be false
|
||||||
|
|
||||||
|
shared.content.gear.flat.head_special_nye.event.start = moment().subtract(5,'days')
|
||||||
|
shared.content.gear.flat.head_special_nye.event.end = moment().add(5,'days')
|
||||||
|
expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be true
|
||||||
|
|
||||||
|
describe 'Rebirth', ->
|
||||||
|
user = undefined
|
||||||
|
it 'removes correct gear', ->
|
||||||
|
user = newUser()
|
||||||
|
user.stats.lvl = 100
|
||||||
|
user.items.gear.owned = {
|
||||||
|
"weapon_warrior_0": true,
|
||||||
|
"weapon_warrior_1": true,
|
||||||
|
"armor_warrior_1": false,
|
||||||
|
"armor_mystery_201402": true,
|
||||||
|
"back_mystery_201402": false,
|
||||||
|
"head_mystery_201402": true,
|
||||||
|
"weapon_armoire_basicCrossbow": true,
|
||||||
|
}
|
||||||
|
user.ops.rebirth()
|
||||||
|
expect(user.items.gear.owned).to.eql {
|
||||||
|
"weapon_warrior_0": true,
|
||||||
|
"weapon_warrior_1": false,
|
||||||
|
"armor_warrior_1": false,
|
||||||
|
"armor_mystery_201402": true,
|
||||||
|
"back_mystery_201402": false,
|
||||||
|
"head_mystery_201402": true,
|
||||||
|
"weapon_armoire_basicCrossbow": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
describe 'store', ->
|
||||||
|
it 'buys a Quest scroll', ->
|
||||||
|
user = newUser()
|
||||||
|
user.stats.gp = 205
|
||||||
|
user.ops.buyQuest {params: {key: 'dilatoryDistress1'}}
|
||||||
|
expect(user.items.quests).to.eql {dilatoryDistress1: 1}
|
||||||
|
expect(user).toHaveGP 5
|
||||||
|
|
||||||
|
it 'does not buy Quests without enough Gold', ->
|
||||||
|
user = newUser()
|
||||||
|
user.stats.gp = 1
|
||||||
|
user.ops.buyQuest {params: {key: 'dilatoryDistress1'}}
|
||||||
|
expect(user.items.quests).to.eql {}
|
||||||
|
expect(user).toHaveGP 1
|
||||||
|
|
||||||
|
it 'does not buy nonexistent Quests', ->
|
||||||
|
user = newUser()
|
||||||
|
user.stats.gp = 9999
|
||||||
|
user.ops.buyQuest {params: {key: 'snarfblatter'}}
|
||||||
|
expect(user.items.quests).to.eql {}
|
||||||
|
expect(user).toHaveGP 9999
|
||||||
|
|
||||||
|
it 'does not buy Gem-premium Quests', ->
|
||||||
|
user = newUser()
|
||||||
|
user.stats.gp = 9999
|
||||||
|
user.ops.buyQuest {params: {key: 'kraken'}}
|
||||||
|
expect(user.items.quests).to.eql {}
|
||||||
|
expect(user).toHaveGP 9999
|
||||||
|
|
||||||
|
describe 'Gem purchases', ->
|
||||||
|
it 'does not purchase items without enough Gems', ->
|
||||||
|
user = newUser()
|
||||||
|
user.ops.purchase {params: {type: 'eggs', key: 'Cactus'}}
|
||||||
|
user.ops.purchase {params: {type: 'gear', key: 'headAccessory_special_foxEars'}}
|
||||||
|
user.ops.unlock {query: {path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars'}}
|
||||||
|
expect(user.items.eggs).to.eql {}
|
||||||
|
expect(user.items.gear.owned).to.eql { weapon_warrior_0: true }
|
||||||
|
|
||||||
|
it 'purchases an egg', ->
|
||||||
|
user = newUser()
|
||||||
|
user.balance = 1
|
||||||
|
user.ops.purchase {params: {type: 'eggs', key: 'Cactus'}}
|
||||||
|
expect(user.items.eggs).to.eql { Cactus: 1}
|
||||||
|
expect(user.balance).to.eql 0.25
|
||||||
|
|
||||||
|
it 'purchases fox ears', ->
|
||||||
|
user = newUser()
|
||||||
|
user.balance = 1
|
||||||
|
user.ops.purchase {params: {type: 'gear', key: 'headAccessory_special_foxEars'}}
|
||||||
|
expect(user.items.gear.owned).to.eql { weapon_warrior_0: true, headAccessory_special_foxEars: true }
|
||||||
|
expect(user.balance).to.eql 0.5
|
||||||
|
|
||||||
|
it 'unlocks all the animal ears at once', ->
|
||||||
|
user = newUser()
|
||||||
|
user.balance = 2
|
||||||
|
user.ops.unlock {query: {path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars'}}
|
||||||
|
expect(user.items.gear.owned).to.eql { weapon_warrior_0: true, headAccessory_special_bearEars: true, headAccessory_special_cactusEars: true, headAccessory_special_foxEars: true, headAccessory_special_lionEars: true, headAccessory_special_pandaEars: true, headAccessory_special_pigEars: true, headAccessory_special_tigerEars: true, headAccessory_special_wolfEars: true}
|
||||||
|
expect(user.balance).to.eql 0.75
|
||||||
|
|
||||||
|
describe 'spells', ->
|
||||||
|
_.each shared.content.spells, (spellClass)->
|
||||||
|
_.each spellClass, (spell)->
|
||||||
|
it "#{spell.text} has valid values", ->
|
||||||
|
expect(spell.target).to.match(/^(task|self|party|user)$/)
|
||||||
|
expect(spell.mana).to.be.an('number')
|
||||||
|
if spell.lvl
|
||||||
|
expect(spell.lvl).to.be.an('number')
|
||||||
|
expect(spell.lvl).to.be.above(0)
|
||||||
|
expect(spell.cast).to.be.a('function')
|
||||||
|
|
||||||
|
describe 'drop system', ->
|
||||||
|
user = null
|
||||||
|
MIN_RANGE_FOR_POTION = 0
|
||||||
|
MAX_RANGE_FOR_POTION = .3
|
||||||
|
MIN_RANGE_FOR_EGG = .4
|
||||||
|
MAX_RANGE_FOR_EGG = .6
|
||||||
|
MIN_RANGE_FOR_FOOD = .7
|
||||||
|
MAX_RANGE_FOR_FOOD = 1
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
user = newUser()
|
||||||
|
user.flags.dropsEnabled = true
|
||||||
|
@task_id = shared.uuid()
|
||||||
|
user.ops.addTask({body: {type: 'daily', id: @task_id}})
|
||||||
|
|
||||||
|
it 'drops a hatching potion', ->
|
||||||
|
for random in [MIN_RANGE_FOR_POTION..MAX_RANGE_FOR_POTION] by .1
|
||||||
|
sinon.stub(user.fns, 'predictableRandom').returns random
|
||||||
|
user.ops.score {params: { id: @task_id, direction: 'up'}}
|
||||||
|
expect(user.items.eggs).to.be.empty
|
||||||
|
expect(user.items.hatchingPotions).to.not.be.empty
|
||||||
|
expect(user.items.food).to.be.empty
|
||||||
|
user.fns.predictableRandom.restore()
|
||||||
|
|
||||||
|
it 'drops a pet egg', ->
|
||||||
|
for random in [MIN_RANGE_FOR_EGG..MAX_RANGE_FOR_EGG] by .1
|
||||||
|
sinon.stub(user.fns, 'predictableRandom').returns random
|
||||||
|
user.ops.score {params: { id: @task_id, direction: 'up'}}
|
||||||
|
expect(user.items.eggs).to.not.be.empty
|
||||||
|
expect(user.items.hatchingPotions).to.be.empty
|
||||||
|
expect(user.items.food).to.be.empty
|
||||||
|
user.fns.predictableRandom.restore()
|
||||||
|
|
||||||
|
it 'drops food', ->
|
||||||
|
for random in [MIN_RANGE_FOR_FOOD..MAX_RANGE_FOR_FOOD] by .1
|
||||||
|
sinon.stub(user.fns, 'predictableRandom').returns random
|
||||||
|
user.ops.score {params: { id: @task_id, direction: 'up'}}
|
||||||
|
expect(user.items.eggs).to.be.empty
|
||||||
|
expect(user.items.hatchingPotions).to.be.empty
|
||||||
|
expect(user.items.food).to.not.be.empty
|
||||||
|
user.fns.predictableRandom.restore()
|
||||||
|
|
||||||
|
it 'does not get a drop', ->
|
||||||
|
sinon.stub(user.fns, 'predictableRandom').returns 0.5
|
||||||
|
user.ops.score {params: { id: @task_id, direction: 'up'}}
|
||||||
|
expect(user.items.eggs).to.eql {}
|
||||||
|
expect(user.items.hatchingPotions).to.eql {}
|
||||||
|
expect(user.items.food).to.eql {}
|
||||||
|
user.fns.predictableRandom.restore()
|
||||||
|
|
||||||
|
describe 'Quests', ->
|
||||||
|
_.each shared.content.quests, (quest)->
|
||||||
|
it "#{quest.text()} has valid values", ->
|
||||||
|
expect(quest.notes()).to.be.an('string')
|
||||||
|
expect(quest.completion()).to.be.an('string') if quest.completion
|
||||||
|
expect(quest.previous).to.be.an('string') if quest.previous
|
||||||
|
expect(quest.value).to.be.greaterThan 0 if quest.canBuy()
|
||||||
|
expect(quest.drop.gp).to.not.be.lessThan 0
|
||||||
|
expect(quest.drop.exp).to.not.be.lessThan 0
|
||||||
|
expect(quest.category).to.match(/pet|unlockable|gold|world/)
|
||||||
|
if quest.drop.items
|
||||||
|
expect(quest.drop.items).to.be.an(Array)
|
||||||
|
if quest.boss
|
||||||
|
expect(quest.boss.name()).to.be.an('string')
|
||||||
|
expect(quest.boss.hp).to.be.greaterThan 0
|
||||||
|
expect(quest.boss.str).to.be.greaterThan 0
|
||||||
|
else if quest.collect
|
||||||
|
_.each quest.collect, (collect)->
|
||||||
|
expect(collect.text()).to.be.an('string')
|
||||||
|
expect(collect.count).to.be.greaterThan 0
|
||||||
|
|
||||||
|
describe 'Achievements', ->
|
||||||
|
_.each shared.content.classes, (klass) ->
|
||||||
|
user = newUser()
|
||||||
|
user.stats.gp = 10000
|
||||||
|
_.each shared.content.gearTypes, (type) ->
|
||||||
|
_.each [1..5], (i) ->
|
||||||
|
user.ops.buy {params:'#{type}_#{klass}_#{i}'}
|
||||||
|
it 'does not get ultimateGear ' + klass, ->
|
||||||
|
expect(user.achievements.ultimateGearSets[klass]).to.not.be.ok()
|
||||||
|
_.each shared.content.gearTypes, (type) ->
|
||||||
|
user.ops.buy {params:'#{type}_#{klass}_6'}
|
||||||
|
xit 'gets ultimateGear ' + klass, ->
|
||||||
|
expect(user.achievements.ultimateGearSets[klass]).to.be.ok()
|
||||||
|
|
||||||
|
it 'does not remove existing Ultimate Gear achievements', ->
|
||||||
|
user = newUser()
|
||||||
|
user.achievements.ultimateGearSets = {'healer':true,'wizard':true,'rogue':true,'warrior':true}
|
||||||
|
user.items.gear.owned.shield_warrior_5 = false
|
||||||
|
user.items.gear.owned.weapon_rogue_6 = false
|
||||||
|
user.ops.buy {params:'shield_warrior_5'}
|
||||||
|
expect(user.achievements.ultimateGearSets).to.eql {'healer':true,'wizard':true,'rogue':true,'warrior':true}
|
||||||
|
|
||||||
|
describe 'unlocking features', ->
|
||||||
|
it 'unlocks drops at level 3', ->
|
||||||
|
user = newUser()
|
||||||
|
user.stats.lvl = 3
|
||||||
|
user.fns.updateStats(user.stats)
|
||||||
|
expect(user.flags.dropsEnabled).to.be.ok()
|
||||||
|
|
||||||
|
it 'unlocks Rebirth at level 50', ->
|
||||||
|
user = newUser()
|
||||||
|
user.stats.lvl = 50
|
||||||
|
user.fns.updateStats(user.stats)
|
||||||
|
expect(user.flags.rebirthEnabled).to.be.ok()
|
||||||
|
|
||||||
|
describe 'level-awarded Quests', ->
|
||||||
|
it 'gets Attack of the Mundane at level 15', ->
|
||||||
|
user = newUser()
|
||||||
|
user.stats.lvl = 15
|
||||||
|
user.fns.updateStats(user.stats)
|
||||||
|
expect(user.flags.levelDrops.atom1).to.be.ok()
|
||||||
|
expect(user.items.quests.atom1).to.eql 1
|
||||||
|
|
||||||
|
it 'gets Vice at level 30', ->
|
||||||
|
user = newUser()
|
||||||
|
user.stats.lvl = 30
|
||||||
|
user.fns.updateStats(user.stats)
|
||||||
|
expect(user.flags.levelDrops.vice1).to.be.ok()
|
||||||
|
expect(user.items.quests.vice1).to.eql 1
|
||||||
|
|
||||||
|
it 'gets Golden Knight at level 40', ->
|
||||||
|
user = newUser()
|
||||||
|
user.stats.lvl = 40
|
||||||
|
user.fns.updateStats(user.stats)
|
||||||
|
expect(user.flags.levelDrops.goldenknight1).to.be.ok()
|
||||||
|
expect(user.items.quests.goldenknight1).to.eql 1
|
||||||
|
|
||||||
|
it 'gets Moonstone Chain at level 60', ->
|
||||||
|
user = newUser()
|
||||||
|
user.stats.lvl = 60
|
||||||
|
user.fns.updateStats(user.stats)
|
||||||
|
expect(user.flags.levelDrops.moonstone1).to.be.ok()
|
||||||
|
expect(user.items.quests.moonstone1).to.eql 1
|
||||||
|
|
||||||
|
describe 'Simple Scoring', ->
|
||||||
|
beforeEach ->
|
||||||
|
{@before, @after} = beforeAfter()
|
||||||
|
|
||||||
|
it 'Habits : Up', ->
|
||||||
|
@after.ops.score {params: {id: @after.habits[0].id, direction: 'down'}, query: {times: 5}}
|
||||||
|
expectLostPoints(@before, @after,'habit')
|
||||||
|
|
||||||
|
it 'Habits : Down', ->
|
||||||
|
@after.ops.score {params: {id: @after.habits[0].id, direction: 'up'}, query: {times: 5}}
|
||||||
|
expectGainedPoints(@before, @after,'habit')
|
||||||
|
|
||||||
|
it 'Dailys : Up', ->
|
||||||
|
@after.ops.score {params: {id: @after.dailys[0].id, direction: 'up'}}
|
||||||
|
expectGainedPoints(@before, @after,'daily')
|
||||||
|
|
||||||
|
it 'Dailys : Up, Down', ->
|
||||||
|
@after.ops.score {params: {id: @after.dailys[0].id, direction: 'up'}}
|
||||||
|
@after.ops.score {params: {id: @after.dailys[0].id, direction: 'down'}}
|
||||||
|
expectClosePoints(@before, @after, 'daily')
|
||||||
|
|
||||||
|
it 'Todos : Up', ->
|
||||||
|
@after.ops.score {params: {id: @after.todos[0].id, direction: 'up'}}
|
||||||
|
expectGainedPoints(@before, @after,'todo')
|
||||||
|
|
||||||
|
it 'Todos : Up, Down', ->
|
||||||
|
@after.ops.score {params: {id: @after.todos[0].id, direction: 'up'}}
|
||||||
|
@after.ops.score {params: {id: @after.todos[0].id, direction: 'down'}}
|
||||||
|
expectClosePoints(@before, @after, 'todo')
|
||||||
|
|
||||||
|
describe 'Cron', ->
|
||||||
|
|
||||||
|
it 'computes shouldCron', ->
|
||||||
|
user = newUser()
|
||||||
|
|
||||||
|
paths = {};user.fns.cron {paths}
|
||||||
|
expect(user.lastCron).to.not.be.ok # it setup the cron property now
|
||||||
|
|
||||||
|
user.lastCron = +moment().subtract(1,'days')
|
||||||
|
|
||||||
|
paths = {};user.fns.cron {paths}
|
||||||
|
expect(user.lastCron).to.be.greaterThan 0
|
||||||
|
|
||||||
|
# user.lastCron = +moment().add(1,'days')
|
||||||
|
# paths = {};algos.cron user, {paths}
|
||||||
|
# expect(paths.lastCron).to.be true # busted cron (was set to after today's date)
|
||||||
|
|
||||||
|
it 'only dailies & todos are affected', ->
|
||||||
|
{before,after} = beforeAfter({daysAgo:1})
|
||||||
|
before.dailys = before.todos = after.dailys = after.todos = []
|
||||||
|
after.fns.cron()
|
||||||
|
before.stats.mp=after.stats.mp #FIXME
|
||||||
|
expect(after.lastCron).to.not.be before.lastCron # make sure cron was run
|
||||||
|
delete after.stats.buffs;delete before.stats.buffs
|
||||||
|
expect(before.stats).to.eql after.stats
|
||||||
|
beforeTasks = before.habits.concat(before.dailys).concat(before.todos).concat(before.rewards)
|
||||||
|
afterTasks = after.habits.concat(after.dailys).concat(after.todos).concat(after.rewards)
|
||||||
|
expect(beforeTasks).to.eql afterTasks
|
||||||
|
|
||||||
|
describe 'preening', ->
|
||||||
|
beforeEach ->
|
||||||
|
@clock = sinon.useFakeTimers(Date.parse("2013-11-20"), "Date")
|
||||||
|
|
||||||
|
afterEach ->
|
||||||
|
@clock.restore()
|
||||||
|
|
||||||
|
it 'should preen user history', ->
|
||||||
|
{before,after} = beforeAfter({daysAgo:1})
|
||||||
|
history = [
|
||||||
|
# Last year should be condensed to one entry, avg: 1
|
||||||
|
{date:'09/01/2012', value: 0}
|
||||||
|
{date:'10/01/2012', value: 0}
|
||||||
|
{date:'11/01/2012', value: 2}
|
||||||
|
{date:'12/01/2012', value: 2}
|
||||||
|
|
||||||
|
# Each month of this year should be condensed to 1/mo, averages follow
|
||||||
|
{date:'01/01/2013', value: 1} #2
|
||||||
|
{date:'01/15/2013', value: 3}
|
||||||
|
|
||||||
|
{date:'02/01/2013', value: 2} #3
|
||||||
|
{date:'02/15/2013', value: 4}
|
||||||
|
|
||||||
|
{date:'03/01/2013', value: 3} #4
|
||||||
|
{date:'03/15/2013', value: 5}
|
||||||
|
|
||||||
|
{date:'04/01/2013', value: 4} #5
|
||||||
|
{date:'04/15/2013', value: 6}
|
||||||
|
|
||||||
|
{date:'05/01/2013', value: 5} #6
|
||||||
|
{date:'05/15/2013', value: 7}
|
||||||
|
|
||||||
|
{date:'06/01/2013', value: 6} #7
|
||||||
|
{date:'06/15/2013', value: 8}
|
||||||
|
|
||||||
|
{date:'07/01/2013', value: 7} #8
|
||||||
|
{date:'07/15/2013', value: 9}
|
||||||
|
|
||||||
|
{date:'08/01/2013', value: 8} #9
|
||||||
|
{date:'08/15/2013', value: 10}
|
||||||
|
|
||||||
|
{date:'09/01/2013', value: 9} #10
|
||||||
|
{date:'09/15/2013', value: 11}
|
||||||
|
|
||||||
|
{date:'010/01/2013', value: 10} #11
|
||||||
|
{date:'010/15/2013', value: 12}
|
||||||
|
|
||||||
|
# This month should condense each week
|
||||||
|
{date:'011/01/2013', value: 12}
|
||||||
|
{date:'011/02/2013', value: 13}
|
||||||
|
{date:'011/03/2013', value: 14}
|
||||||
|
{date:'011/04/2013', value: 15}
|
||||||
|
]
|
||||||
|
after.history = {exp: _.cloneDeep(history), todos: _.cloneDeep(history)}
|
||||||
|
after.habits[0].history = _.cloneDeep(history)
|
||||||
|
after.fns.cron()
|
||||||
|
|
||||||
|
# remove history entries created by cron
|
||||||
|
after.history.exp.pop()
|
||||||
|
after.history.todos.pop()
|
||||||
|
|
||||||
|
_.each [after.history.exp, after.history.todos, after.habits[0].history], (arr) ->
|
||||||
|
expect(_.map(arr, (x)->x.value)).to.eql [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
||||||
|
|
||||||
|
describe 'Todos', ->
|
||||||
|
it '1 day missed', ->
|
||||||
|
{before,after} = beforeAfter({daysAgo:1})
|
||||||
|
before.dailys = after.dailys = []
|
||||||
|
after.fns.cron()
|
||||||
|
|
||||||
|
# todos don't effect stats
|
||||||
|
expect(after).toHaveHP 50
|
||||||
|
expect(after).toHaveExp 0
|
||||||
|
expect(after).toHaveGP 0
|
||||||
|
|
||||||
|
# but they devalue
|
||||||
|
expect(before.todos[0].value).to.be 0 # sanity check for task setup
|
||||||
|
expect(after.todos[0].value).to.be -1 # the actual test
|
||||||
|
expect(after.history.todos).to.have.length 1
|
||||||
|
|
||||||
|
it '2 days missed', ->
|
||||||
|
{before,after} = beforeAfter({daysAgo:2})
|
||||||
|
before.dailys = after.dailys = []
|
||||||
|
after.fns.cron()
|
||||||
|
|
||||||
|
# todos devalue by only one day's worth of devaluation
|
||||||
|
expect(before.todos[0].value).to.be 0 # sanity check for task setup
|
||||||
|
expect(after.todos[0].value).to.be -1 # the actual test
|
||||||
|
|
||||||
|
# I used hard-coded dates here instead of 'now' so the tests don't fail
|
||||||
|
# when you run them between midnight and dayStart. Nothing worse than
|
||||||
|
# intermittent failures.
|
||||||
|
describe 'cron day calculations', ->
|
||||||
|
dayStart = 4
|
||||||
|
fstr = "YYYY-MM-DD HH:mm:ss"
|
||||||
|
|
||||||
|
it 'startOfDay before dayStart', ->
|
||||||
|
# If the time is before dayStart, then we expect the start of the day to be yesterday at dayStart
|
||||||
|
start = shared.startOfDay {now: moment('2014-10-09 02:30:00'), dayStart}
|
||||||
|
expect(start.format(fstr)).to.eql '2014-10-08 04:00:00'
|
||||||
|
|
||||||
|
it 'startOfDay after dayStart', ->
|
||||||
|
# If the time is after dayStart, then we expect the start of the day to be today at dayStart
|
||||||
|
start = shared.startOfDay {now: moment('2014-10-09 05:30:00'), dayStart}
|
||||||
|
expect(start.format(fstr)).to.eql '2014-10-09 04:00:00'
|
||||||
|
|
||||||
|
it 'daysSince cron before, now after', ->
|
||||||
|
# If the lastCron was before dayStart, then a time on the same day after dayStart
|
||||||
|
# should be 1 day later than lastCron
|
||||||
|
lastCron = moment('2014-10-09 02:30:00')
|
||||||
|
days = shared.daysSince(lastCron, {now: moment('2014-10-09 11:30:00'), dayStart})
|
||||||
|
expect(days).to.eql 1
|
||||||
|
|
||||||
|
it 'daysSince cron before, now before', ->
|
||||||
|
# If the lastCron was before dayStart, then a time on the same day also before dayStart
|
||||||
|
# should be 0 days later than lastCron
|
||||||
|
lastCron = moment('2014-10-09 02:30:00')
|
||||||
|
days = shared.daysSince(lastCron, {now: moment('2014-10-09 03:30:00'), dayStart})
|
||||||
|
expect(days).to.eql 0
|
||||||
|
|
||||||
|
it 'daysSince cron after, now after', ->
|
||||||
|
# If the lastCron was after dayStart, then a time on the same day also after dayStart
|
||||||
|
# should be 0 days later than lastCron
|
||||||
|
lastCron = moment('2014-10-09 05:30:00')
|
||||||
|
days = shared.daysSince(lastCron, {now: moment('2014-10-09 06:30:00'), dayStart})
|
||||||
|
expect(days).to.eql 0
|
||||||
|
|
||||||
|
it 'daysSince cron after, now tomorrow before', ->
|
||||||
|
# If the lastCron was after dayStart, then a time on the following day but before dayStart
|
||||||
|
# should be 0 days later than lastCron
|
||||||
|
lastCron = moment('2014-10-09 12:30:00')
|
||||||
|
days = shared.daysSince(lastCron, {now: moment('2014-10-10 01:30:00'), dayStart})
|
||||||
|
expect(days).to.eql 0
|
||||||
|
|
||||||
|
it 'daysSince cron after, now tomorrow after', ->
|
||||||
|
# If the lastCron was after dayStart, then a time on the following day and after dayStart
|
||||||
|
# should be 1 day later than lastCron
|
||||||
|
lastCron = moment('2014-10-09 12:30:00')
|
||||||
|
days = shared.daysSince(lastCron, {now: moment('2014-10-10 10:30:00'), dayStart})
|
||||||
|
expect(days).to.eql 1
|
||||||
|
|
||||||
|
xit 'daysSince, last cron before new dayStart', ->
|
||||||
|
# If lastCron was after dayStart (at 1am) with dayStart set at 0, changing dayStart to 4am
|
||||||
|
# should not trigger another cron the same day
|
||||||
|
|
||||||
|
# dayStart is 0
|
||||||
|
lastCron = moment('2014-10-09 01:00:00')
|
||||||
|
# dayStart is 4
|
||||||
|
days = shared.daysSince(lastCron, {now: moment('2014-10-09 05:00:00'), dayStart})
|
||||||
|
expect(days).to.eql 0
|
||||||
|
|
||||||
|
describe 'dailies', ->
|
||||||
|
|
||||||
|
describe 'new day', ->
|
||||||
|
|
||||||
|
###
|
||||||
|
This section runs through a "cron matrix" of all permutations (that I can easily account for). It sets
|
||||||
|
task due days, user custom day start, timezoneOffset, etc - then runs cron, jumps to tomorrow and runs cron,
|
||||||
|
and so on - testing each possible outcome along the way
|
||||||
|
###
|
||||||
|
|
||||||
|
runCron = (options) ->
|
||||||
|
_.each [480, 240, 0, -120], (timezoneOffset) -> # test different timezones
|
||||||
|
now = shared.startOfWeek({timezoneOffset}).add(options.currentHour||0, 'hours')
|
||||||
|
{before,after} = beforeAfter({now, timezoneOffset, daysAgo:1, cronAfterStart:options.cronAfterStart||true, dayStart:options.dayStart||0, limitOne:'daily'})
|
||||||
|
before.dailys[0].repeat = after.dailys[0].repeat = options.repeat if options.repeat
|
||||||
|
before.dailys[0].streak = after.dailys[0].streak = 10
|
||||||
|
before.dailys[0].completed = after.dailys[0].completed = true if options.checked
|
||||||
|
before.dailys[0].startDate = after.dailys[0].startDate = moment().subtract(30, 'days')
|
||||||
|
if options.shouldDo
|
||||||
|
expect(shared.shouldDo(now.toDate(), after.dailys[0], {timezoneOffset, dayStart:options.dayStart, now})).to.be.ok()
|
||||||
|
after.fns.cron {now}
|
||||||
|
before.stats.mp=after.stats.mp #FIXME
|
||||||
|
switch options.expect
|
||||||
|
when 'losePoints' then expectLostPoints(before,after,'daily')
|
||||||
|
when 'noChange' then expectNoChange(before,after)
|
||||||
|
when 'noDamage' then expectDayResetNoDamage(before,after)
|
||||||
|
{before,after}
|
||||||
|
|
||||||
|
# These test cases were written assuming that lastCron was run after dayStart
|
||||||
|
# even if currentHour < dayStart and lastCron = yesterday at currentHour.
|
||||||
|
# cronAfterStart makes sure that lastCron is moved to be after dayStart.
|
||||||
|
cronMatrix =
|
||||||
|
steps:
|
||||||
|
|
||||||
|
'due yesterday':
|
||||||
|
defaults: {daysAgo:1, cronAfterStart:true, limitOne: 'daily'}
|
||||||
|
steps:
|
||||||
|
|
||||||
|
'(simple)': {expect:'losePoints'}
|
||||||
|
|
||||||
|
'due today':
|
||||||
|
# NOTE: a strange thing here, moment().startOf('week') is Sunday, but moment.zone(myTimeZone).startOf('week') is Monday.
|
||||||
|
defaults: {repeat:{su:true,m:true,t:true,w:true,th:true,f:true,s:true}}
|
||||||
|
steps:
|
||||||
|
'pre-dayStart':
|
||||||
|
defaults: {currentHour:3, dayStart:4, shouldDo:true}
|
||||||
|
steps:
|
||||||
|
'checked': {checked: true, expect:'noChange'}
|
||||||
|
'un-checked': {checked: false, expect:'noChange'}
|
||||||
|
'post-dayStart':
|
||||||
|
defaults: {currentHour:5, dayStart:4, shouldDo:true}
|
||||||
|
steps:
|
||||||
|
'checked': {checked:true, expect:'noDamage'}
|
||||||
|
'unchecked': {checked:false, expect: 'losePoints'}
|
||||||
|
|
||||||
|
'NOT due today':
|
||||||
|
defaults: {repeat:{su:true,m:false,t:true,w:true,th:true,f:true,s:true}}
|
||||||
|
steps:
|
||||||
|
'pre-dayStart':
|
||||||
|
defaults: {currentHour:3, dayStart:4, shouldDo:true}
|
||||||
|
steps:
|
||||||
|
'checked': {checked: true, expect:'noChange'}
|
||||||
|
'un-checked': {checked: false, expect:'noChange'}
|
||||||
|
'post-dayStart':
|
||||||
|
defaults: {currentHour:5, dayStart:4, shouldDo:false}
|
||||||
|
steps:
|
||||||
|
'checked': {checked:true, expect:'noDamage'}
|
||||||
|
'unchecked': {checked:false, expect: 'losePoints'}
|
||||||
|
|
||||||
|
'not due yesterday':
|
||||||
|
defaults: repeatWithoutLastWeekday()
|
||||||
|
steps:
|
||||||
|
'(simple)': {expect:'noDamage'}
|
||||||
|
'post-dayStart': {currentHour:5,dayStart:4, expect:'noDamage'}
|
||||||
|
'pre-dayStart': {currentHour:3, dayStart:4, expect:'noChange'}
|
||||||
|
|
||||||
|
recurseCronMatrix = (obj, options={}) ->
|
||||||
|
if obj.steps
|
||||||
|
_.each obj.steps, (step, text) ->
|
||||||
|
o = _.cloneDeep options
|
||||||
|
o.text ?= ''; o.text += " #{text} "
|
||||||
|
recurseCronMatrix step, _.defaults(o,obj.defaults)
|
||||||
|
else
|
||||||
|
it "#{options.text}", -> runCron(_.defaults(obj,options))
|
||||||
|
recurseCronMatrix(cronMatrix)
|
||||||
|
|
||||||
|
describe 'Helper', ->
|
||||||
|
|
||||||
|
it 'calculates gold coins', ->
|
||||||
|
expect(shared.gold(10)).to.eql 10
|
||||||
|
expect(shared.gold(1.957)).to.eql 1
|
||||||
|
expect(shared.gold()).to.eql 0
|
||||||
|
|
||||||
|
it 'calculates silver coins', ->
|
||||||
|
expect(shared.silver(10)).to.eql 0
|
||||||
|
expect(shared.silver(1.957)).to.eql 95
|
||||||
|
expect(shared.silver(0.01)).to.eql "01"
|
||||||
|
expect(shared.silver()).to.eql "00"
|
||||||
|
|
||||||
|
it 'calculates experience to next level', ->
|
||||||
|
expect(shared.tnl 1).to.eql 150
|
||||||
|
expect(shared.tnl 2).to.eql 160
|
||||||
|
expect(shared.tnl 10).to.eql 260
|
||||||
|
expect(shared.tnl 99).to.eql 3580
|
||||||
|
|
||||||
|
it 'calculates the start of the day', ->
|
||||||
|
fstr = 'YYYY-MM-DD HH:mm:ss'
|
||||||
|
today = '2013-01-01 00:00:00'
|
||||||
|
# get the timezone for the day, so the test case doesn't fail
|
||||||
|
# if you run it during daylight savings time because by default
|
||||||
|
# it uses moment().zone() which is the current minute offset
|
||||||
|
zone = moment(today).zone()
|
||||||
|
expect(shared.startOfDay({now: new Date(2013, 0, 1, 0)}, timezoneOffset:zone).format(fstr)).to.eql today
|
||||||
|
expect(shared.startOfDay({now: new Date(2013, 0, 1, 5)}, timezoneOffset:zone).format(fstr)).to.eql today
|
||||||
|
expect(shared.startOfDay({now: new Date(2013, 0, 1, 23, 59, 59), timezoneOffset:zone}).format(fstr)).to.eql today
|
||||||
@@ -10,7 +10,7 @@ moment = require('moment');
|
|||||||
|
|
||||||
shared = require('../../common/script/index.js');
|
shared = require('../../common/script/index.js');
|
||||||
|
|
||||||
shared.i18n.translations = require('../../website/src/libs/i18n.js').translations;
|
shared.i18n.translations = require('../../website/src/libs/api-v2/i18n.js').translations;
|
||||||
|
|
||||||
test_helper = require('./test_helper');
|
test_helper = require('./test_helper');
|
||||||
|
|
||||||
|
|||||||
418
test/common/dailies.coffee
Normal file
418
test/common/dailies.coffee
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
_ = require 'lodash'
|
||||||
|
expect = require 'expect.js'
|
||||||
|
sinon = require 'sinon'
|
||||||
|
moment = require 'moment'
|
||||||
|
shared = require '../../common/script/index.js'
|
||||||
|
shared.i18n.translations = require('../../website/src/libs/api-v2/i18n.js').translations
|
||||||
|
|
||||||
|
repeatWithoutLastWeekday = ()->
|
||||||
|
repeat = {su:true,m:true,t:true,w:true,th:true,f:true,s:true}
|
||||||
|
if shared.startOfWeek(moment().zone(0)).isoWeekday() == 1 # Monday
|
||||||
|
repeat.su = false
|
||||||
|
else
|
||||||
|
repeat.s = false
|
||||||
|
{repeat: repeat}
|
||||||
|
|
||||||
|
### Helper Functions ####
|
||||||
|
# @TODO: Refactor into helper file
|
||||||
|
newUser = (addTasks=true)->
|
||||||
|
buffs = {per:0, int:0, con:0, str:0, stealth: 0, streaks: false}
|
||||||
|
user =
|
||||||
|
auth:
|
||||||
|
timestamps: {}
|
||||||
|
stats: {str:1, con:1, per:1, int:1, mp: 32, class: 'warrior', buffs: buffs}
|
||||||
|
items:
|
||||||
|
lastDrop:
|
||||||
|
count: 0
|
||||||
|
hatchingPotions: {}
|
||||||
|
eggs: {}
|
||||||
|
food: {}
|
||||||
|
gear:
|
||||||
|
equipped: {}
|
||||||
|
costume: {}
|
||||||
|
party:
|
||||||
|
quest:
|
||||||
|
progress:
|
||||||
|
down: 0
|
||||||
|
preferences: {}
|
||||||
|
dailys: []
|
||||||
|
todos: []
|
||||||
|
rewards: []
|
||||||
|
flags: {}
|
||||||
|
achievements: {}
|
||||||
|
contributor:
|
||||||
|
level: 2
|
||||||
|
shared.wrap(user)
|
||||||
|
user.ops.reset(null, ->)
|
||||||
|
if addTasks
|
||||||
|
_.each ['habit', 'todo', 'daily'], (task)->
|
||||||
|
user.ops.addTask {body: {type: task, id: shared.uuid()}}
|
||||||
|
user
|
||||||
|
|
||||||
|
cron = (usr, missedDays=1) ->
|
||||||
|
usr.lastCron = moment().subtract(missedDays,'days')
|
||||||
|
usr.fns.cron()
|
||||||
|
|
||||||
|
describe 'daily/weekly that repeats everyday (default)', ->
|
||||||
|
user = null
|
||||||
|
daily = null
|
||||||
|
weekly = null
|
||||||
|
|
||||||
|
describe 'when startDate is in the future', ->
|
||||||
|
beforeEach ->
|
||||||
|
user = newUser()
|
||||||
|
user.dailys = [
|
||||||
|
shared.taskDefaults({type:'daily', startDate: moment().add(7, 'days'), frequency: 'daily'})
|
||||||
|
shared.taskDefaults({type:'daily', startDate: moment().add(7, 'days'), frequency: 'weekly', repeat: {su:true,m:true,t:true,w:true,th:true,f:true,s:true}})
|
||||||
|
]
|
||||||
|
daily = user.dailys[0]
|
||||||
|
weekly = user.dailys[1]
|
||||||
|
|
||||||
|
it 'does not damage user for not completing it', ->
|
||||||
|
cron(user)
|
||||||
|
expect(user.stats.hp).to.be 50
|
||||||
|
|
||||||
|
it 'does not change value on cron if daily is incomplete', ->
|
||||||
|
cron(user)
|
||||||
|
expect(daily.value).to.be 0
|
||||||
|
expect(weekly.value).to.be 0
|
||||||
|
|
||||||
|
it 'does not reset checklists if daily is not marked as complete', ->
|
||||||
|
checklist = [
|
||||||
|
{
|
||||||
|
'text' : '1',
|
||||||
|
'id' : 'checklist-one',
|
||||||
|
'completed' : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text' : '2',
|
||||||
|
'id' : 'checklist-two',
|
||||||
|
'completed' : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text' : '3',
|
||||||
|
'id' : 'checklist-three',
|
||||||
|
'completed' : false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
daily.checklist = checklist
|
||||||
|
weekly.checklist = checklist
|
||||||
|
cron(user)
|
||||||
|
|
||||||
|
expect(daily.checklist[0].completed).to.be true
|
||||||
|
expect(daily.checklist[1].completed).to.be true
|
||||||
|
expect(daily.checklist[2].completed).to.be false
|
||||||
|
|
||||||
|
expect(weekly.checklist[0].completed).to.be true
|
||||||
|
expect(weekly.checklist[1].completed).to.be true
|
||||||
|
expect(weekly.checklist[2].completed).to.be false
|
||||||
|
|
||||||
|
it 'resets checklists if daily is marked as complete', ->
|
||||||
|
checklist = [
|
||||||
|
{
|
||||||
|
'text' : '1',
|
||||||
|
'id' : 'checklist-one',
|
||||||
|
'completed' : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text' : '2',
|
||||||
|
'id' : 'checklist-two',
|
||||||
|
'completed' : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text' : '3',
|
||||||
|
'id' : 'checklist-three',
|
||||||
|
'completed' : false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
daily.checklist = checklist
|
||||||
|
weekly.checklist = checklist
|
||||||
|
daily.completed = true
|
||||||
|
weekly.completed = true
|
||||||
|
cron(user)
|
||||||
|
|
||||||
|
_.each daily.checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
_.each weekly.checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
it 'is due on startDate', ->
|
||||||
|
daily_due_today = shared.shouldDo moment(), daily
|
||||||
|
daily_due_on_start_date = shared.shouldDo moment().add(7, 'days'), daily
|
||||||
|
|
||||||
|
expect(daily_due_today).to.be false
|
||||||
|
expect(daily_due_on_start_date).to.be true
|
||||||
|
|
||||||
|
weekly_due_today = shared.shouldDo moment(), weekly
|
||||||
|
weekly_due_on_start_date = shared.shouldDo moment().add(7, 'days'), weekly
|
||||||
|
|
||||||
|
expect(weekly_due_today).to.be false
|
||||||
|
expect(weekly_due_on_start_date).to.be true
|
||||||
|
|
||||||
|
describe 'when startDate is in the past', ->
|
||||||
|
beforeEach ->
|
||||||
|
user = newUser()
|
||||||
|
user.dailys = [
|
||||||
|
shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days'), frequency: 'daily'})
|
||||||
|
shared.taskDefaults({type:'daily', startDate: moment().subtract(7, 'days'), frequency: 'weekly'})
|
||||||
|
]
|
||||||
|
daily = user.dailys[0]
|
||||||
|
weekly = user.dailys[1]
|
||||||
|
|
||||||
|
it 'does damage user for not completing it', ->
|
||||||
|
cron(user)
|
||||||
|
expect(user.stats.hp).to.be.lessThan 50
|
||||||
|
|
||||||
|
it 'decreases value on cron if daily is incomplete', ->
|
||||||
|
cron(user, 1)
|
||||||
|
expect(daily.value).to.be -1
|
||||||
|
expect(weekly.value).to.be -1
|
||||||
|
|
||||||
|
it 'decreases value on cron once only if daily is incomplete and multiple days are missed', ->
|
||||||
|
cron(user, 7)
|
||||||
|
expect(daily.value).to.be -1
|
||||||
|
expect(weekly.value).to.be -1
|
||||||
|
|
||||||
|
it 'resets checklists if daily is not marked as complete', ->
|
||||||
|
checklist = [
|
||||||
|
{
|
||||||
|
'text' : '1',
|
||||||
|
'id' : 'checklist-one',
|
||||||
|
'completed' : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text' : '2',
|
||||||
|
'id' : 'checklist-two',
|
||||||
|
'completed' : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text' : '3',
|
||||||
|
'id' : 'checklist-three',
|
||||||
|
'completed' : false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
daily.checklist = checklist
|
||||||
|
weekly.checklist = checklist
|
||||||
|
cron(user)
|
||||||
|
|
||||||
|
_.each daily.checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
_.each weekly.checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
it 'resets checklists if daily is marked as complete', ->
|
||||||
|
checklist = [
|
||||||
|
{
|
||||||
|
'text' : '1',
|
||||||
|
'id' : 'checklist-one',
|
||||||
|
'completed' : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text' : '2',
|
||||||
|
'id' : 'checklist-two',
|
||||||
|
'completed' : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text' : '3',
|
||||||
|
'id' : 'checklist-three',
|
||||||
|
'completed' : false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
daily.checklist = checklist
|
||||||
|
daily.completed = true
|
||||||
|
weekly.checklist = checklist
|
||||||
|
weekly.completed = true
|
||||||
|
cron(user)
|
||||||
|
|
||||||
|
_.each daily.checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
_.each weekly.checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
describe 'when startDate is today', ->
|
||||||
|
beforeEach ->
|
||||||
|
user = newUser()
|
||||||
|
user.dailys = [
|
||||||
|
# Must set start date to yesterday, because cron mock sets last cron to yesterday
|
||||||
|
shared.taskDefaults({type:'daily', startDate: moment().subtract(1, 'days'), frequency: 'daily'})
|
||||||
|
shared.taskDefaults({type:'daily', startDate: moment().subtract(1, 'days'), frequency: 'weekly'})
|
||||||
|
]
|
||||||
|
daily = user.dailys[0]
|
||||||
|
weekly = user.dailys[1]
|
||||||
|
|
||||||
|
it 'does damage user for not completing it', ->
|
||||||
|
cron(user)
|
||||||
|
expect(user.stats.hp).to.be.lessThan 50
|
||||||
|
|
||||||
|
it 'decreases value on cron if daily is incomplete', ->
|
||||||
|
cron(user)
|
||||||
|
expect(daily.value).to.be.lessThan 0
|
||||||
|
expect(weekly.value).to.be.lessThan 0
|
||||||
|
|
||||||
|
it 'resets checklists if daily is not marked as complete', ->
|
||||||
|
checklist = [
|
||||||
|
{
|
||||||
|
'text' : '1',
|
||||||
|
'id' : 'checklist-one',
|
||||||
|
'completed' : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text' : '2',
|
||||||
|
'id' : 'checklist-two',
|
||||||
|
'completed' : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text' : '3',
|
||||||
|
'id' : 'checklist-three',
|
||||||
|
'completed' : false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
daily.checklist = checklist
|
||||||
|
weekly.checklist = checklist
|
||||||
|
cron(user)
|
||||||
|
|
||||||
|
_.each daily.checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
_.each weekly.checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
it 'resets checklists if daily is marked as complete', ->
|
||||||
|
checklist = [
|
||||||
|
{
|
||||||
|
'text' : '1',
|
||||||
|
'id' : 'checklist-one',
|
||||||
|
'completed' : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text' : '2',
|
||||||
|
'id' : 'checklist-two',
|
||||||
|
'completed' : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text' : '3',
|
||||||
|
'id' : 'checklist-three',
|
||||||
|
'completed' : false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
daily.checklist = checklist
|
||||||
|
daily.completed = true
|
||||||
|
weekly.checklist = checklist
|
||||||
|
weekly.completed = true
|
||||||
|
cron(user)
|
||||||
|
|
||||||
|
_.each daily.checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
_.each weekly.checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
describe 'daily that repeats every x days', ->
|
||||||
|
user = null
|
||||||
|
daily = null
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
user = newUser()
|
||||||
|
user.dailys = [ shared.taskDefaults({type:'daily', startDate: moment(), frequency: 'daily'}) ]
|
||||||
|
daily = user.dailys[0]
|
||||||
|
|
||||||
|
_.times 11, (due) ->
|
||||||
|
|
||||||
|
it 'where x equals ' + due, ->
|
||||||
|
daily.everyX = due
|
||||||
|
|
||||||
|
_.times 30, (day) ->
|
||||||
|
isDue = shared.shouldDo moment().add(day, 'days'), daily
|
||||||
|
expect(isDue).to.be true if day % due == 0
|
||||||
|
expect(isDue).to.be false if day % due != 0
|
||||||
|
|
||||||
|
describe 'daily that repeats every X days when multiple days are missed', ->
|
||||||
|
everyX = 3
|
||||||
|
startDateDaysAgo = everyX * 3
|
||||||
|
user = null
|
||||||
|
daily = null
|
||||||
|
|
||||||
|
describe 'including missing a due date', ->
|
||||||
|
missedDays = everyX * 2 + 1
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
user = newUser()
|
||||||
|
user.dailys = [
|
||||||
|
shared.taskDefaults({type:'daily', startDate: moment().subtract(startDateDaysAgo, 'days'), frequency: 'daily', everyX: everyX})
|
||||||
|
]
|
||||||
|
daily = user.dailys[0]
|
||||||
|
|
||||||
|
it 'decreases value on cron once only if daily is incomplete', ->
|
||||||
|
cron(user, missedDays)
|
||||||
|
expect(daily.value).to.be -1
|
||||||
|
|
||||||
|
it 'resets checklists if daily is incomplete', ->
|
||||||
|
checklist = [
|
||||||
|
{
|
||||||
|
'text' : '1',
|
||||||
|
'id' : 'checklist-one',
|
||||||
|
'completed' : true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
daily.checklist = checklist
|
||||||
|
cron(user, missedDays)
|
||||||
|
_.each daily.checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
it 'resets checklists if daily is marked as complete', ->
|
||||||
|
checklist = [
|
||||||
|
{
|
||||||
|
'text' : '1',
|
||||||
|
'id' : 'checklist-one',
|
||||||
|
'completed' : true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
daily.checklist = checklist
|
||||||
|
daily.completed = true
|
||||||
|
cron(user, missedDays)
|
||||||
|
_.each daily.checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
|
|
||||||
|
describe 'but not missing a due date', ->
|
||||||
|
missedDays = everyX - 1
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
user = newUser()
|
||||||
|
user.dailys = [
|
||||||
|
shared.taskDefaults({type:'daily', startDate: moment().subtract(startDateDaysAgo, 'days'), frequency: 'daily', everyX: everyX})
|
||||||
|
]
|
||||||
|
daily = user.dailys[0]
|
||||||
|
|
||||||
|
it 'does not decrease value on cron', ->
|
||||||
|
cron(user, missedDays)
|
||||||
|
expect(daily.value).to.be 0
|
||||||
|
|
||||||
|
it 'does not reset checklists if daily is incomplete', ->
|
||||||
|
checklist = [
|
||||||
|
{
|
||||||
|
'text' : '1',
|
||||||
|
'id' : 'checklist-one',
|
||||||
|
'completed' : true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
daily.checklist = checklist
|
||||||
|
cron(user, missedDays)
|
||||||
|
_.each daily.checklist, (box)->
|
||||||
|
expect(box.completed).to.be true
|
||||||
|
|
||||||
|
it 'resets checklists if daily is marked as complete', ->
|
||||||
|
checklist = [
|
||||||
|
{
|
||||||
|
'text' : '1',
|
||||||
|
'id' : 'checklist-one',
|
||||||
|
'completed' : true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
daily.checklist = checklist
|
||||||
|
daily.completed = true
|
||||||
|
cron(user, missedDays)
|
||||||
|
_.each daily.checklist, (box)->
|
||||||
|
expect(box.completed).to.be false
|
||||||
@@ -10,7 +10,7 @@ moment = require('moment');
|
|||||||
|
|
||||||
shared = require('../../common/script/index.js');
|
shared = require('../../common/script/index.js');
|
||||||
|
|
||||||
shared.i18n.translations = require('../../website/src/libs/i18n.js').translations;
|
shared.i18n.translations = require('../../website/src/libs/api-v2/i18n.js').translations;
|
||||||
|
|
||||||
repeatWithoutLastWeekday = function() {
|
repeatWithoutLastWeekday = function() {
|
||||||
var repeat;
|
var repeat;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
var shared = require('../../common/script/index.js');
|
var shared = require('../../common/script/index.js');
|
||||||
shared.i18n.translations = require('../../website/src/libs/i18n.js').translations
|
shared.i18n.translations = require('../../website/src/libs/api-v2/i18n.js').translations
|
||||||
|
|
||||||
require('./test_helper');
|
require('./test_helper');
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {MongoClient as mongo} from 'mongodb';
|
|||||||
import {v4 as generateUUID} from 'uuid';
|
import {v4 as generateUUID} from 'uuid';
|
||||||
import superagent from 'superagent';
|
import superagent from 'superagent';
|
||||||
import i18n from '../../common/script/src/i18n';
|
import i18n from '../../common/script/src/i18n';
|
||||||
i18n.translations = require('../../website/src/libs/i18n.js').translations;
|
i18n.translations = require('../../website/src/libs/api-v3/i18n').translations;
|
||||||
|
|
||||||
const API_TEST_SERVER_PORT = 3003;
|
const API_TEST_SERVER_PORT = 3003;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { model as User } from '../../website/src/models/user'
|
|||||||
import { model as Group } from '../../website/src/models/group'
|
import { model as Group } from '../../website/src/models/group'
|
||||||
import i18n from '../../common/script/src/i18n';
|
import i18n from '../../common/script/src/i18n';
|
||||||
require('coffee-script');
|
require('coffee-script');
|
||||||
i18n.translations = require('../../website/src/libs/i18n.js').translations;
|
i18n.translations = require('../../website/src/libs/api-v3/i18n.js').translations;
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
@@ -35,6 +35,7 @@ export function generateReq(options={}) {
|
|||||||
let defaultReq = {
|
let defaultReq = {
|
||||||
body: {},
|
body: {},
|
||||||
query: {},
|
query: {},
|
||||||
|
headers: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
return defaults(options, defaultReq);
|
return defaults(options, defaultReq);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ require('./globals.helper');
|
|||||||
import {each} from 'lodash';
|
import {each} from 'lodash';
|
||||||
|
|
||||||
import i18n from '../../common/script/src/i18n';
|
import i18n from '../../common/script/src/i18n';
|
||||||
i18n.translations = require('../../website/src/libs/i18n.js').translations;
|
i18n.translations = require('../../website/src/libs/api-v3/i18n').translations;
|
||||||
|
|
||||||
export const STRING_ERROR_MSG = 'Error processing the string. Please see Help > Report a Bug.';
|
export const STRING_ERROR_MSG = 'Error processing the string. Please see Help > Report a Bug.';
|
||||||
export const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/;
|
export const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ var Group = require('../../../website/src/models/group').model;
|
|||||||
var groupsController = require('../../../website/src/controllers/api-v2/groups');
|
var groupsController = require('../../../website/src/controllers/api-v2/groups');
|
||||||
|
|
||||||
describe('Groups Controller', function() {
|
describe('Groups Controller', function() {
|
||||||
var utils = require('../../../website/src/libs/utils');
|
var utils = require('../../../website/src/libs/api-v2/utils');
|
||||||
|
|
||||||
describe('#invite', function() {
|
describe('#invite', function() {
|
||||||
var res, req, user, group;
|
var res, req, user, group;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ chai.use(require("sinon-chai"))
|
|||||||
var expect = chai.expect
|
var expect = chai.expect
|
||||||
var rewire = require('rewire');
|
var rewire = require('rewire');
|
||||||
|
|
||||||
var webhook = rewire('../../website/src/libs/webhook');
|
var webhook = rewire('../../website/src/libs/api-v2/webhook');
|
||||||
|
|
||||||
describe('webhooks', function() {
|
describe('webhooks', function() {
|
||||||
var postSpy;
|
var postSpy;
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ var validator = require('validator');
|
|||||||
var passport = require('passport');
|
var passport = require('passport');
|
||||||
var shared = require('../../../../common');
|
var shared = require('../../../../common');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var utils = require('../../libs/utils');
|
var utils = require('../../libs/api-v2/utils');
|
||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var FirebaseTokenGenerator = require('firebase-token-generator');
|
var FirebaseTokenGenerator = require('firebase-token-generator');
|
||||||
var User = require('../../models/user').model;
|
var User = require('../../models/user').model;
|
||||||
var EmailUnsubscription = require('../../models/emailUnsubscription').model;
|
var EmailUnsubscription = require('../../models/emailUnsubscription').model;
|
||||||
var analytics = utils.analytics;
|
var analytics = utils.analytics;
|
||||||
var i18n = require('./../../libs/i18n');
|
var i18n = require('./../../libs/api-v2/i18n');
|
||||||
|
|
||||||
var isProd = nconf.get('NODE_ENV') === 'production';
|
var isProd = nconf.get('NODE_ENV') === 'production';
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ var Group = require('./../../models/group').model;
|
|||||||
var Challenge = require('./../../models/challenge').model;
|
var Challenge = require('./../../models/challenge').model;
|
||||||
var logging = require('./../../libs/api-v2/logging');
|
var logging = require('./../../libs/api-v2/logging');
|
||||||
var csv = require('express-csv');
|
var csv = require('express-csv');
|
||||||
var utils = require('../../libs/utils');
|
var utils = require('../../libs/api-v2/utils');
|
||||||
var api = module.exports;
|
var api = module.exports;
|
||||||
var pushNotify = require('./../pushNotifications');
|
var pushNotify = require('./../pushNotifications');
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ var _ = require('lodash');
|
|||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var Q = require('q');
|
var Q = require('q');
|
||||||
var utils = require('./../../libs/utils');
|
var utils = require('./../../libs/api-v2/utils');
|
||||||
var shared = require('../../../../common');
|
var shared = require('../../../../common');
|
||||||
var User = require('./../../models/user').model;
|
var User = require('./../../models/user').model;
|
||||||
var Group = require('./../../models/group').model;
|
var Group = require('./../../models/group').model;
|
||||||
@@ -19,7 +19,7 @@ var isProd = nconf.get('NODE_ENV') === 'production';
|
|||||||
var api = module.exports;
|
var api = module.exports;
|
||||||
var pushNotify = require('./../pushNotifications');
|
var pushNotify = require('./../pushNotifications');
|
||||||
var analytics = utils.analytics;
|
var analytics = utils.analytics;
|
||||||
var firebase = require('../../libs/firebase');
|
var firebase = require('../../libs/api-v2/firebase');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ var api = module.exports;
|
|||||||
var async = require('async');
|
var async = require('async');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var shared = require('../../../../common');
|
var shared = require('../../../../common');
|
||||||
var utils = require('../../libs/utils');
|
var utils = require('../../libs/api-v2/utils');
|
||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var pushNotify = require('./../pushNotifications');
|
var pushNotify = require('./../pushNotifications');
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
var User = require('../../models/user').model;
|
var User = require('../../models/user').model;
|
||||||
var EmailUnsubscription = require('../../models/emailUnsubscription').model;
|
var EmailUnsubscription = require('../../models/emailUnsubscription').model;
|
||||||
var utils = require('../../libs/utils');
|
var utils = require('../../libs/api-v2/utils');
|
||||||
var i18n = require('../../../../common').i18n;
|
var i18n = require('../../../../common').i18n;
|
||||||
|
|
||||||
var api = module.exports = {};
|
var api = module.exports = {};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ var nconf = require('nconf');
|
|||||||
var async = require('async');
|
var async = require('async');
|
||||||
var shared = require('../../../../common');
|
var shared = require('../../../../common');
|
||||||
var User = require('./../../models/user').model;
|
var User = require('./../../models/user').model;
|
||||||
var utils = require('./../../libs/utils');
|
var utils = require('./../../libs/api-v2/utils');
|
||||||
var analytics = utils.analytics;
|
var analytics = utils.analytics;
|
||||||
var Group = require('./../../models/group').model;
|
var Group = require('./../../models/group').model;
|
||||||
var Challenge = require('./../../models/challenge').model;
|
var Challenge = require('./../../models/challenge').model;
|
||||||
@@ -13,7 +13,7 @@ var moment = require('moment');
|
|||||||
var logging = require('./../../libs/api-v2/logging');
|
var logging = require('./../../libs/api-v2/logging');
|
||||||
var acceptablePUTPaths;
|
var acceptablePUTPaths;
|
||||||
var api = module.exports;
|
var api = module.exports;
|
||||||
var firebase = require('../../libs/firebase');
|
var firebase = require('../../libs/api-v2/firebase');
|
||||||
var webhook = require('../../libs/api-v2/webhook');
|
var webhook = require('../../libs/api-v2/webhook');
|
||||||
|
|
||||||
// api.purchase // Shared.ops
|
// api.purchase // Shared.ops
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var shared = require('../../../../common');
|
var shared = require('../../../../common');
|
||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var utils = require('./../../libs/utils');
|
var utils = require('./../../libs/api-v2/utils');
|
||||||
var moment = require('moment');
|
var moment = require('moment');
|
||||||
var isProduction = nconf.get("NODE_ENV") === "production";
|
var isProduction = nconf.get("NODE_ENV") === "production";
|
||||||
var stripe = require('./stripe');
|
var stripe = require('./stripe');
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ var logger = require('./libs/api-v3/logger');
|
|||||||
// Initialize configuration
|
// Initialize configuration
|
||||||
var setupNconf = require('./libs/api-v3/setupNconf');
|
var setupNconf = require('./libs/api-v3/setupNconf');
|
||||||
setupNconf();
|
setupNconf();
|
||||||
var utils = require('./libs/utils');
|
|
||||||
utils.setupConfig();
|
|
||||||
|
|
||||||
var IS_PROD = nconf.get('IS_PROD');
|
var IS_PROD = nconf.get('IS_PROD');
|
||||||
var IS_DEV = nconf.get('IS_DEV');
|
var IS_DEV = nconf.get('IS_DEV');
|
||||||
var cores = Number(nconf.get('WEB_CONCURRENCY')) || 0;
|
var cores = Number(nconf.get('WEB_CONCURRENCY')) || 0;
|
||||||
|
|
||||||
|
if (IS_DEV) Error.stackTraceLimit = Infinity;
|
||||||
|
|
||||||
// Setup the cluster module
|
// Setup the cluster module
|
||||||
if (cores !== 0 && cluster.isMaster && (IS_DEV || IS_PROD)) {
|
if (cores !== 0 && cluster.isMaster && (IS_DEV || IS_PROD)) {
|
||||||
// Fork workers. If config.json has CORES=x, use that - otherwise, use all cpus-1 (production)
|
// Fork workers. If config.json has CORES=x, use that - otherwise, use all cpus-1 (production)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
require('../i18n');
|
require('./i18n');
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var Content = require('../../../common').content;
|
var Content = require('../../../../common').content;
|
||||||
var Amplitude = require('amplitude');
|
var Amplitude = require('amplitude');
|
||||||
var googleAnalytics = require('universal-analytics');
|
var googleAnalytics = require('universal-analytics');
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ var fs = require('fs');
|
|||||||
var path = require('path');
|
var path = require('path');
|
||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var manifestFiles = require("../../public/manifest.json");
|
var manifestFiles = require("../../../public/manifest.json");
|
||||||
|
|
||||||
var IS_PROD = nconf.get('NODE_ENV') === 'production';
|
var IS_PROD = nconf.get('NODE_ENV') === 'production';
|
||||||
var buildFiles = [];
|
var buildFiles = [];
|
||||||
@@ -15,7 +15,7 @@ var walk = function(folder){
|
|||||||
if(fs.statSync(file).isDirectory()){
|
if(fs.statSync(file).isDirectory()){
|
||||||
walk(file);
|
walk(file);
|
||||||
}else{
|
}else{
|
||||||
var relFolder = path.relative(path.join(__dirname, "/../../build"), folder);
|
var relFolder = path.relative(path.join(__dirname, "/../../../build"), folder);
|
||||||
var old = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1');
|
var old = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1');
|
||||||
|
|
||||||
if(relFolder){
|
if(relFolder){
|
||||||
@@ -28,7 +28,7 @@ var walk = function(folder){
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
walk(path.join(__dirname, "/../../build"));
|
walk(path.join(__dirname, "/../../../build"));
|
||||||
|
|
||||||
var getBuildUrl = module.exports.getBuildUrl = function(url){
|
var getBuildUrl = module.exports.getBuildUrl = function(url){
|
||||||
if(buildFiles[url]) return '/' + buildFiles[url];
|
if(buildFiles[url]) return '/' + buildFiles[url];
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
var fs = require('fs'),
|
var fs = require('fs'),
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
_ = require('lodash'),
|
_ = require('lodash'),
|
||||||
User = require('../models/user').model,
|
User = require('../../models/user').model,
|
||||||
accepts = require('accepts'),
|
accepts = require('accepts'),
|
||||||
shared = require('../../../common'),
|
shared = require('../../../../common'),
|
||||||
translations = {};
|
translations = {};
|
||||||
|
|
||||||
var localePath = path.join(__dirname, "/../../../common/locales/")
|
var localePath = path.join(__dirname, "/../../../../common/locales/")
|
||||||
|
|
||||||
var loadTranslations = function(locale){
|
var loadTranslations = function(locale){
|
||||||
var files = fs.readdirSync(path.join(localePath, locale));
|
var files = fs.readdirSync(path.join(localePath, locale));
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
import { content as Content } from '../../../../common';
|
import { content as Content } from '../../../../common';
|
||||||
|
|
||||||
require('coffee-script');
|
require('coffee-script');
|
||||||
require('../../libs/i18n');
|
|
||||||
|
|
||||||
const AMPLIUDE_TOKEN = nconf.get('AMPLITUDE_KEY');
|
const AMPLIUDE_TOKEN = nconf.get('AMPLITUDE_KEY');
|
||||||
const GA_TOKEN = nconf.get('GA_ID');
|
const GA_TOKEN = nconf.get('GA_ID');
|
||||||
|
|||||||
62
website/src/libs/api-v3/buildManifest.js
Normal file
62
website/src/libs/api-v3/buildManifest.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
|
||||||
|
const MANIFEST_FILE_PATH = path.join(__dirname, '/../../../public/manifest.json');
|
||||||
|
const BUILD_FOLDER_PATH = path.join(__dirname, '/../../../build');
|
||||||
|
let manifestFiles = require(MANIFEST_FILE_PATH);
|
||||||
|
|
||||||
|
const IS_PROD = nconf.get('IS_PROD');
|
||||||
|
let buildFiles = [];
|
||||||
|
|
||||||
|
function _walk (folder) {
|
||||||
|
let files = fs.readdirSync(folder);
|
||||||
|
|
||||||
|
files.forEach((fileName) => {
|
||||||
|
let file = `${folder}/${fileName}`;
|
||||||
|
|
||||||
|
if (fs.statSync(file).isDirectory()) {
|
||||||
|
_walk(file);
|
||||||
|
} else {
|
||||||
|
let relFolder = path.relative(BUILD_FOLDER_PATH, folder);
|
||||||
|
let original = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1'); // Match the hash part of the filename
|
||||||
|
|
||||||
|
if (relFolder) {
|
||||||
|
original = `${relFolder}/${original}`;
|
||||||
|
fileName = `${relFolder}/${fileName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFiles[original] = fileName;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walks through all the files in the build directory
|
||||||
|
// and creates a map of original files names and hashed files names
|
||||||
|
_walk(BUILD_FOLDER_PATH);
|
||||||
|
|
||||||
|
export function getBuildUrl (url) {
|
||||||
|
return `/${buildFiles[url] || url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getManifestFiles (page) {
|
||||||
|
let files = manifestFiles[page];
|
||||||
|
|
||||||
|
if (!files) throw new Error(`Page "${page}" not found!`);
|
||||||
|
|
||||||
|
let htmlCode = '';
|
||||||
|
|
||||||
|
if (IS_PROD) {
|
||||||
|
htmlCode += `<link rel="stylesheet" type="text/css" href="${getBuildUrl(page + '.css')}">`; // eslint-disable-line prefer-template
|
||||||
|
htmlCode += `<script type="text/javascript" src="${getBuildUrl(page + '.js')}"></script>`; // eslint-disable-line prefer-template
|
||||||
|
} else {
|
||||||
|
files.css.forEach((file) => {
|
||||||
|
htmlCode += `<link rel="stylesheet" type="text/css" href="${getBuildUrl(file)}">`;
|
||||||
|
});
|
||||||
|
files.js.forEach((file) => {
|
||||||
|
htmlCode += `<script type="text/javascript" src="${getBuildUrl(file)}"></script>`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return htmlCode;
|
||||||
|
}
|
||||||
158
website/src/libs/api-v3/email.js
Normal file
158
website/src/libs/api-v3/email.js
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import { createTransport } from 'nodemailer';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
import logger from './logger';
|
||||||
|
import { encrypt } from './encryption';
|
||||||
|
import request from 'request';
|
||||||
|
|
||||||
|
const IS_PROD = nconf.get('IS_PROD');
|
||||||
|
const EMAIL_SERVER = {
|
||||||
|
url: nconf.get('EMAIL_SERVER:url'),
|
||||||
|
auth: {
|
||||||
|
user: nconf.get('EMAIL_SERVER:authUser'),
|
||||||
|
password: nconf.get('EMAIL_SERVER:authPassword'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const BASE_URL = nconf.get('BASE_URL');
|
||||||
|
|
||||||
|
let smtpTransporter = createTransport({
|
||||||
|
service: nconf.get('SMTP_SERVICE'),
|
||||||
|
auth: {
|
||||||
|
user: nconf.get('SMTP_USER'),
|
||||||
|
pass: nconf.get('SMTP_PASS'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send email directly from the server using the smtpTransporter,
|
||||||
|
// used only to send password reset emails because users unsubscribed on Mandrill wouldn't get them
|
||||||
|
export function send (mailData) {
|
||||||
|
return smtpTransporter
|
||||||
|
.sendMail(mailData)
|
||||||
|
.catch((error) => logger.error(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUserInfo (user, fields = []) {
|
||||||
|
let info = {};
|
||||||
|
|
||||||
|
if (fields.indexOf('name') !== -1) {
|
||||||
|
info.name = user.profile && user.profile.name;
|
||||||
|
|
||||||
|
if (!info.name) {
|
||||||
|
if (user.auth.local && user.auth.local.username) {
|
||||||
|
info.name = user.auth.local.username;
|
||||||
|
} else if (user.auth.facebook) {
|
||||||
|
info.name = user.auth.facebook.displayName || user.auth.facebook.username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields.indexOf('email') !== -1) {
|
||||||
|
if (user.auth.local && user.auth.local.email) {
|
||||||
|
info.email = user.auth.local.email;
|
||||||
|
} else if (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0] && user.auth.facebook.emails[0].value) {
|
||||||
|
info.email = user.auth.facebook.emails[0].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields.indexOf('_id') !== -1) {
|
||||||
|
info._id = user._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields.indexOf('canSend') !== -1) {
|
||||||
|
if (user.preferences && user.preferences.emailNotifications) {
|
||||||
|
info.canSend = user.preferences.emailNotifications.unsubscribeFromAll !== true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a transactional email using Mandrill through the external email server
|
||||||
|
export function sendTxn (mailingInfoArray, emailType, variables, personalVariables) {
|
||||||
|
mailingInfoArray = Array.isArray(mailingInfoArray) ? mailingInfoArray : [mailingInfoArray];
|
||||||
|
|
||||||
|
variables = [
|
||||||
|
{name: 'BASE_URL', content: BASE_URL},
|
||||||
|
].concat(variables || []);
|
||||||
|
|
||||||
|
// It's important to pass at least a user with its `preferences` as we need to check if he unsubscribed
|
||||||
|
mailingInfoArray = mailingInfoArray.map((mailingInfo) => {
|
||||||
|
return mailingInfo._id ? getUserInfo(mailingInfo, ['_id', 'email', 'name', 'canSend']) : mailingInfo;
|
||||||
|
}).filter((mailingInfo) => {
|
||||||
|
// Always send reset-password emails
|
||||||
|
// Don't check canSend for non registered users as already checked before
|
||||||
|
return mailingInfo.email && (!mailingInfo._id || mailingInfo.canSend || emailType === 'reset-password');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Personal variables are personal to each email recipient, if they are missing
|
||||||
|
// we manually create a structure for them with RECIPIENT_NAME and RECIPIENT_UNSUB_URL
|
||||||
|
// otherwise we just add RECIPIENT_NAME and RECIPIENT_UNSUB_URL to the existing personal variables
|
||||||
|
if (!personalVariables || personalVariables.length === 0) {
|
||||||
|
personalVariables = mailingInfoArray.map((mailingInfo) => {
|
||||||
|
return {
|
||||||
|
rcpt: mailingInfo.email,
|
||||||
|
vars: [
|
||||||
|
{
|
||||||
|
name: 'RECIPIENT_NAME',
|
||||||
|
content: mailingInfo.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'RECIPIENT_UNSUB_URL',
|
||||||
|
content: `/unsubscribe?code=${encrypt(JSON.stringify({
|
||||||
|
_id: mailingInfo._id,
|
||||||
|
email: mailingInfo.email,
|
||||||
|
}))}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let temporaryPersonalVariables = {};
|
||||||
|
|
||||||
|
mailingInfoArray.forEach((mailingInfo) => {
|
||||||
|
temporaryPersonalVariables[mailingInfo.email] = {
|
||||||
|
name: mailingInfo.name,
|
||||||
|
_id: mailingInfo._id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
personalVariables.forEach((singlePersonalVariables) => {
|
||||||
|
singlePersonalVariables.vars.push(
|
||||||
|
{
|
||||||
|
name: 'RECIPIENT_NAME',
|
||||||
|
content: temporaryPersonalVariables[singlePersonalVariables.rcpt].name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'RECIPIENT_UNSUB_URL',
|
||||||
|
content: `/unsubscribe?code=${encrypt(JSON.stringify({
|
||||||
|
_id: temporaryPersonalVariables[singlePersonalVariables.rcpt]._id,
|
||||||
|
email: singlePersonalVariables.rcpt,
|
||||||
|
}))}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_PROD && mailingInfoArray.length > 0) {
|
||||||
|
request.post({
|
||||||
|
url: `${EMAIL_SERVER.url}/job`,
|
||||||
|
auth: {
|
||||||
|
user: EMAIL_SERVER.auth.user,
|
||||||
|
pass: EMAIL_SERVER.auth.password,
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
type: 'email',
|
||||||
|
data: {
|
||||||
|
emailType,
|
||||||
|
to: mailingInfoArray,
|
||||||
|
variables,
|
||||||
|
personalVariables,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
priority: 'high',
|
||||||
|
attempts: 5,
|
||||||
|
backoff: {delay: 10 * 60 * 1000, type: 'fixed'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, (err) => logger.error(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
25
website/src/libs/api-v3/encryption.js
Normal file
25
website/src/libs/api-v3/encryption.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
createCipher,
|
||||||
|
createDecipher,
|
||||||
|
} from 'crypto';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
|
||||||
|
// TODO check this is secure
|
||||||
|
const algorithm = 'aes-256-ctr';
|
||||||
|
const SESSION_SECRET = nconf.get('SESSION_SECRET');
|
||||||
|
|
||||||
|
export function encrypt (text) {
|
||||||
|
let cipher = createCipher(algorithm, SESSION_SECRET);
|
||||||
|
let crypted = cipher.update(text, 'utf8', 'hex');
|
||||||
|
|
||||||
|
crypted += cipher.final('hex');
|
||||||
|
return crypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decrypt (text) {
|
||||||
|
let decipher = createDecipher(algorithm, SESSION_SECRET);
|
||||||
|
let dec = decipher.update(text, 'hex', 'utf8');
|
||||||
|
|
||||||
|
dec += decipher.final('utf8');
|
||||||
|
return dec;
|
||||||
|
}
|
||||||
67
website/src/libs/api-v3/firebase.js
Normal file
67
website/src/libs/api-v3/firebase.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import Firebase from 'firebase';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
const FIREBASE_CONFIG = nconf.get('FIREBASE');
|
||||||
|
const FIREBASE_ENABLED = FIREBASE_CONFIG.ENABLED === 'true';
|
||||||
|
|
||||||
|
let firebaseRef;
|
||||||
|
|
||||||
|
if (FIREBASE_ENABLED) {
|
||||||
|
firebaseRef = new Firebase(`https://${FIREBASE_CONFIG.APP}.firebaseio.com`);
|
||||||
|
|
||||||
|
// TODO what happens if an op is sent before client is authenticated?
|
||||||
|
firebaseRef.authWithCustomToken(FIREBASE_CONFIG.SECRET, (err) => {
|
||||||
|
// TODO it's ok to kill the server here? what if FB is offline?
|
||||||
|
if (err) throw new Error('Impossible to authenticate Firebase');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateGroupData (group) {
|
||||||
|
if (!FIREBASE_ENABLED) return;
|
||||||
|
// TODO is throw ok? we don't have callbacks
|
||||||
|
if (!group) throw new Error('group obj is required.');
|
||||||
|
// Return in case of tavern (comparison working because we use string for _id)
|
||||||
|
if (group._id === 'habitrpg') return;
|
||||||
|
|
||||||
|
firebaseRef.child(`rooms/${group._id}`)
|
||||||
|
.set({
|
||||||
|
name: group.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addUserToGroup (groupId, userId) {
|
||||||
|
if (!FIREBASE_ENABLED) return;
|
||||||
|
if (!userId || !groupId) throw new Error('groupId, userId are required.');
|
||||||
|
if (groupId === 'habitrpg') return;
|
||||||
|
|
||||||
|
firebaseRef.child(`members/${groupId}/${userId}`).set(true);
|
||||||
|
firebaseRef.child(`users/${userId}/rooms/${groupId}`).set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeUserFromGroup (groupId, userId) {
|
||||||
|
if (!FIREBASE_ENABLED) return;
|
||||||
|
if (!userId || !groupId) throw new Error('groupId, userId are required.');
|
||||||
|
if (groupId === 'habitrpg') return;
|
||||||
|
|
||||||
|
firebaseRef.child(`members/${groupId}/${userId}`).remove();
|
||||||
|
firebaseRef.child(`users/${userId}/rooms/${groupId}`).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteGroup (groupId) {
|
||||||
|
if (!FIREBASE_ENABLED) return;
|
||||||
|
if (!groupId) throw new Error('groupId is required.');
|
||||||
|
if (groupId === 'habitrpg') return;
|
||||||
|
|
||||||
|
firebaseRef.child(`members/${groupId}`).remove();
|
||||||
|
// FIXME not really necessary as long as we only store room data,
|
||||||
|
// as empty objects are automatically deleted (/members/... in future...)
|
||||||
|
firebaseRef.child(`rooms/${groupId}`).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME not really necessary as long as we only store room data,
|
||||||
|
// as empty objects are automatically deleted
|
||||||
|
export function deleteUser (userId) {
|
||||||
|
if (!FIREBASE_ENABLED) return;
|
||||||
|
if (!userId) throw new Error('userId is required.');
|
||||||
|
|
||||||
|
firebaseRef.child(`users/${userId}`).remove();
|
||||||
|
}
|
||||||
118
website/src/libs/api-v3/i18n.js
Normal file
118
website/src/libs/api-v3/i18n.js
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import shared from '../../../../common';
|
||||||
|
|
||||||
|
export const localePath = path.join(__dirname, '/../../../../common/locales/');
|
||||||
|
|
||||||
|
// Store translations
|
||||||
|
export let translations = {};
|
||||||
|
// Store MomentJS localization files
|
||||||
|
export let momentLangs = {};
|
||||||
|
|
||||||
|
// Handle differencies in language codes between MomentJS and /locales
|
||||||
|
let momentLangsMapping = {
|
||||||
|
en: 'en-gb',
|
||||||
|
en_GB: 'en-gb', // eslint-disable-line camelcase
|
||||||
|
no: 'nn',
|
||||||
|
zh: 'zh-cn',
|
||||||
|
es_419: 'es', // eslint-disable-line camelcase
|
||||||
|
};
|
||||||
|
|
||||||
|
function _loadTranslations (locale) {
|
||||||
|
let files = fs.readdirSync(path.join(localePath, locale));
|
||||||
|
|
||||||
|
translations[locale] = {};
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
if (path.extname(file) !== '.json') return;
|
||||||
|
|
||||||
|
// We use require to load and parse a JSON file
|
||||||
|
_.merge(translations[locale], require(path.join(localePath, locale, file))); // eslint-disable-line global-require
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// First fetch English strings so we can merge them with missing strings in other languages
|
||||||
|
_loadTranslations('en');
|
||||||
|
|
||||||
|
// Then load all other languages
|
||||||
|
fs.readdirSync(localePath).forEach((file) => {
|
||||||
|
if (file === 'en' || fs.statSync(path.join(localePath, file)).isDirectory() === false) return;
|
||||||
|
_loadTranslations(file);
|
||||||
|
|
||||||
|
// Merge missing strings from english
|
||||||
|
_.defaults(translations[file], translations.en);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add translations to shared
|
||||||
|
shared.i18n.translations = translations;
|
||||||
|
|
||||||
|
export let langCodes = Object.keys(translations);
|
||||||
|
|
||||||
|
export let avalaibleLanguages = langCodes.map((langCode) => {
|
||||||
|
return {
|
||||||
|
code: langCode,
|
||||||
|
name: translations[langCode].languageName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
langCodes.forEach((code) => {
|
||||||
|
let lang = _.find(avalaibleLanguages, {code});
|
||||||
|
|
||||||
|
lang.momentLangCode = momentLangsMapping[code] || code;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// MomentJS lang files are JS files that has to be executed in the browser so we load them as plain text files
|
||||||
|
// We wrap everything in a try catch because the file might not exist
|
||||||
|
let f = fs.readFileSync(path.join(__dirname, `/../../../node_modules/moment/locale/${lang.momentLangCode}.js`), 'utf8');
|
||||||
|
|
||||||
|
momentLangs[code] = f;
|
||||||
|
} catch (e) { // eslint-disable-lint no-empty
|
||||||
|
// TODO implement some type of error loggin?
|
||||||
|
// The catch block is mandatory so can't be removed
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove en_GB from langCodes checked by browser to avoid it being
|
||||||
|
// used in place of plain original 'en' (it's an optional language that can be enabled only in setting)
|
||||||
|
export let defaultLangCodes = _.without(langCodes, 'en_GB');
|
||||||
|
|
||||||
|
// A map of languages that have different versions and the relative versions
|
||||||
|
export let multipleVersionsLanguages = {
|
||||||
|
es: {
|
||||||
|
'es-419': 'es_419',
|
||||||
|
'es-mx': 'es_419',
|
||||||
|
'es-gt': 'es_419',
|
||||||
|
'es-cr': 'es_419',
|
||||||
|
'es-pa': 'es_419',
|
||||||
|
'es-do': 'es_419',
|
||||||
|
'es-ve': 'es_419',
|
||||||
|
'es-co': 'es_419',
|
||||||
|
'es-pe': 'es_419',
|
||||||
|
'es-ar': 'es_419',
|
||||||
|
'es-ec': 'es_419',
|
||||||
|
'es-cl': 'es_419',
|
||||||
|
'es-uy': 'es_419',
|
||||||
|
'es-py': 'es_419',
|
||||||
|
'es-bo': 'es_419',
|
||||||
|
'es-sv': 'es_419',
|
||||||
|
'es-hn': 'es_419',
|
||||||
|
'es-ni': 'es_419',
|
||||||
|
'es-pr': 'es_419',
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
'zh-tw': 'zh_TW',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export en strings only, temporary solution for mobile
|
||||||
|
// This is copied from middlewares/locals#t()
|
||||||
|
// TODO review if this can be removed since the old mobile app is no longer active
|
||||||
|
// stringName and vars are the allowed parameters
|
||||||
|
export function enTranslations (...args) {
|
||||||
|
let language = _.find(avalaibleLanguages, {code: 'en'});
|
||||||
|
|
||||||
|
// language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
|
||||||
|
args.push(language.code);
|
||||||
|
return shared.i18n.t(...args);
|
||||||
|
}
|
||||||
@@ -12,7 +12,9 @@ if (IS_PROD) {
|
|||||||
// log errors to console too
|
// log errors to console too
|
||||||
} else {
|
} else {
|
||||||
logger
|
logger
|
||||||
.add(winston.transports.Console);
|
.add(winston.transports.Console, {
|
||||||
|
colorize: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default logger;
|
export default logger;
|
||||||
|
|||||||
16
website/src/middlewares/api-v3/domain.js
Normal file
16
website/src/middlewares/api-v3/domain.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// TODO in api-v2 this module also checked memory usage every x minutes and
|
||||||
|
// threw an error in case of low memory avalible (possible memory leak)
|
||||||
|
// it's yet to be decided whether to keep it or not
|
||||||
|
import domainMiddleware from 'domain-middleware';
|
||||||
|
|
||||||
|
export default function implementDomainMiddleware (server, mongoose) {
|
||||||
|
return domainMiddleware({
|
||||||
|
server: {
|
||||||
|
close () {
|
||||||
|
server.close();
|
||||||
|
mongoose.connection.close();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
killTimeout: 10000,
|
||||||
|
});
|
||||||
|
}
|
||||||
79
website/src/middlewares/api-v3/getUserLanguage.js
Normal file
79
website/src/middlewares/api-v3/getUserLanguage.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { model as User } from '../../models/user';
|
||||||
|
import accepts from 'accepts';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import {
|
||||||
|
translations,
|
||||||
|
defaultLangCodes,
|
||||||
|
multipleVersionsLanguages,
|
||||||
|
} from '../../libs/api-v3/i18n';
|
||||||
|
|
||||||
|
function _getUniqueListOfLanguages (languages) {
|
||||||
|
let acceptableLanguages = _(languages).map((lang) => {
|
||||||
|
return lang.slice(0, 2);
|
||||||
|
}).uniq().value();
|
||||||
|
|
||||||
|
let uniqueListOfLanguages = _.intersection(acceptableLanguages, defaultLangCodes);
|
||||||
|
|
||||||
|
return uniqueListOfLanguages;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _checkForApplicableLanguageVariant (originalLanguageOptions) {
|
||||||
|
let languageVariant = _.find(originalLanguageOptions, (accepted) => {
|
||||||
|
let trimmedAccepted = accepted.slice(0, 2);
|
||||||
|
|
||||||
|
return multipleVersionsLanguages[trimmedAccepted];
|
||||||
|
});
|
||||||
|
|
||||||
|
return languageVariant;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getFromBrowser (req) {
|
||||||
|
let originalLanguageOptions = accepts(req).languages();
|
||||||
|
let uniqueListOfLanguages = _getUniqueListOfLanguages(originalLanguageOptions);
|
||||||
|
let baseLanguage = (uniqueListOfLanguages[0] || '').toLowerCase();
|
||||||
|
let languageMapping = multipleVersionsLanguages[baseLanguage];
|
||||||
|
|
||||||
|
if (languageMapping) {
|
||||||
|
let languageVariant = _checkForApplicableLanguageVariant(originalLanguageOptions);
|
||||||
|
|
||||||
|
if (languageVariant) {
|
||||||
|
languageVariant = languageVariant.toLowerCase();
|
||||||
|
} else {
|
||||||
|
return 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
return languageMapping[languageVariant] || baseLanguage;
|
||||||
|
} else {
|
||||||
|
return baseLanguage || 'en';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getFromUser (user, req) {
|
||||||
|
let preferredLang = user && user.preferences && user.preferences.language;
|
||||||
|
let lang = translations[preferredLang] ? preferredLang : _getFromBrowser(req);
|
||||||
|
|
||||||
|
return lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function getUserLanguage (req, res, next) {
|
||||||
|
if (req.query.lang) { // In case the language is specified in the request url, use it
|
||||||
|
req.language = translations[req.query.lang] ? req.query.lang : 'en';
|
||||||
|
return next();
|
||||||
|
} else if (req.locals && req.locals.user) { // If the request is authenticated, use the user's preferred language
|
||||||
|
req.language = _getFromUser(req.locals.user, req);
|
||||||
|
return next();
|
||||||
|
} else if (req.session && req.session.userId) { // Same thing if the user has a valid session
|
||||||
|
User.findOne({
|
||||||
|
_id: req.session.userId,
|
||||||
|
}, 'preferences.language')
|
||||||
|
.exec()
|
||||||
|
.then((user) => {
|
||||||
|
req.language = _getFromUser(user, req);
|
||||||
|
return next();
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
} else { // Otherwise get from browser
|
||||||
|
req.language = _getFromUser(null, req);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var utils = require('../libs/utils');
|
var utils = require('../libs/api-v2/utils');
|
||||||
var shared = require('../../../common');
|
var shared = require('../../../common');
|
||||||
var i18n = require('../libs/i18n');
|
var i18n = require('../libs/api-v2/i18n');
|
||||||
var buildManifest = require('../libs/buildManifest');
|
var buildManifest = require('../libs/api-v2/buildManifest');
|
||||||
var shared = require('../../../common');
|
var shared = require('../../../common');
|
||||||
var forceRefresh = require('./forceRefresh');
|
var forceRefresh = require('./forceRefresh');
|
||||||
var tavernQuest = require('../models/group').tavernQuest;
|
var tavernQuest = require('../models/group').tavernQuest;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ var _ = require('lodash');
|
|||||||
var async = require('async');
|
var async = require('async');
|
||||||
var logging = require('../libs/api-v2/logging');
|
var logging = require('../libs/api-v2/logging');
|
||||||
var Challenge = require('./../models/challenge').model;
|
var Challenge = require('./../models/challenge').model;
|
||||||
var firebase = require('../libs/firebase');
|
var firebase = require('../libs/api-v2/firebase');
|
||||||
|
|
||||||
// NOTE any change to groups' members in MongoDB will have to be run through the API
|
// NOTE any change to groups' members in MongoDB will have to be run through the API
|
||||||
// changes made directly to the db will cause Firebase to get out of sync
|
// changes made directly to the db will cause Firebase to get out of sync
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
var auth = require('../../controllers/api-v2/auth');
|
var auth = require('../../controllers/api-v2/auth');
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var i18n = require('../../libs/i18n');
|
var i18n = require('../../libs/api-v2/i18n');
|
||||||
var router = new express.Router();
|
var router = new express.Router();
|
||||||
|
|
||||||
/* auth.auth*/
|
/* auth.auth*/
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ var express = require('express');
|
|||||||
var router = new express.Router();
|
var router = new express.Router();
|
||||||
var auth = require('../../controllers/api-v2/auth');
|
var auth = require('../../controllers/api-v2/auth');
|
||||||
var coupon = require('../../controllers/api-v2/coupon');
|
var coupon = require('../../controllers/api-v2/coupon');
|
||||||
var i18n = require('../../libs/i18n');
|
var i18n = require('../../libs/api-v2/i18n');
|
||||||
|
|
||||||
router.get('/api/v2/coupons', auth.authWithUrl, i18n.getUserLanguage, coupon.ensureAdmin, coupon.getCoupons);
|
router.get('/api/v2/coupons', auth.authWithUrl, i18n.getUserLanguage, coupon.ensureAdmin, coupon.getCoupons);
|
||||||
router.post('/api/v2/coupons/generate/:event', auth.auth, i18n.getUserLanguage, coupon.ensureAdmin, coupon.generateCoupons);
|
router.post('/api/v2/coupons/generate/:event', auth.auth, i18n.getUserLanguage, coupon.ensureAdmin, coupon.generateCoupons);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ var nconf = require("nconf");
|
|||||||
var cron = user.cron;
|
var cron = user.cron;
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var content = require('../../../../common').content;
|
var content = require('../../../../common').content;
|
||||||
var i18n = require('../../libs/i18n');
|
var i18n = require('../../libs/api-v2/i18n');
|
||||||
var forceRefresh = require('../../middlewares/forceRefresh').middleware;
|
var forceRefresh = require('../../middlewares/forceRefresh').middleware;
|
||||||
|
|
||||||
module.exports = function(swagger, v2) {
|
module.exports = function(swagger, v2) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
var express = require('express');
|
var express = require('express');
|
||||||
var router = new express.Router();
|
var router = new express.Router();
|
||||||
var i18n = require('../../libs/i18n');
|
var i18n = require('../../libs/api-v2/i18n');
|
||||||
var unsubscription = require('../../controllers/api-v2/unsubscription');
|
var unsubscription = require('../../controllers/api-v2/unsubscription');
|
||||||
|
|
||||||
router.get('/unsubscribe', i18n.getUserLanguage, unsubscription.unsubscribe);
|
router.get('/unsubscribe', i18n.getUserLanguage, unsubscription.unsubscribe);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ var router = new express.Router();
|
|||||||
var dataexport = require('../controllers/dataexport');
|
var dataexport = require('../controllers/dataexport');
|
||||||
var auth = require('../controllers/api-v2/auth');
|
var auth = require('../controllers/api-v2/auth');
|
||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var i18n = require('../libs/i18n');
|
var i18n = require('../libs/api-v2/i18n');
|
||||||
var locals = require('../middlewares/locals');
|
var locals = require('../middlewares/locals');
|
||||||
|
|
||||||
/* Data export */
|
/* Data export */
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ var express = require('express');
|
|||||||
var router = new express.Router();
|
var router = new express.Router();
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var locals = require('../middlewares/locals');
|
var locals = require('../middlewares/locals');
|
||||||
var i18n = require('../libs/i18n');
|
var i18n = require('../libs/api-v2/i18n');
|
||||||
|
|
||||||
// -------- App --------
|
// -------- App --------
|
||||||
router.get('/', i18n.getUserLanguage, locals, function(req, res) {
|
router.get('/', i18n.getUserLanguage, locals, function(req, res) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ var express = require('express');
|
|||||||
var router = new express.Router();
|
var router = new express.Router();
|
||||||
var auth = require('../controllers/api-v2/auth');
|
var auth = require('../controllers/api-v2/auth');
|
||||||
var payments = require('../controllers/payments');
|
var payments = require('../controllers/payments');
|
||||||
var i18n = require('../libs/i18n');
|
var i18n = require('../libs/api-v2/i18n');
|
||||||
|
|
||||||
router.get('/paypal/checkout', auth.authWithUrl, i18n.getUserLanguage, payments.paypalCheckout);
|
router.get('/paypal/checkout', auth.authWithUrl, i18n.getUserLanguage, payments.paypalCheckout);
|
||||||
router.get('/paypal/checkout/success', i18n.getUserLanguage, payments.paypalCheckoutSuccess);
|
router.get('/paypal/checkout/success', i18n.getUserLanguage, payments.paypalCheckoutSuccess);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import logger from './libs/api-v3/logger';
|
import logger from './libs/api-v3/logger';
|
||||||
import utils from './libs/utils';
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
// import path from 'path';
|
// import path from 'path';
|
||||||
@@ -13,11 +12,11 @@ import passport from 'passport';
|
|||||||
import passportFacebook from 'passport-facebook';
|
import passportFacebook from 'passport-facebook';
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import Q from 'q';
|
import Q from 'q';
|
||||||
|
import domainMiddleware from './middlewares/api-v3/domain';
|
||||||
import attachMiddlewares from './middlewares/api-v3/index';
|
import attachMiddlewares from './middlewares/api-v3/index';
|
||||||
utils.setupConfig();
|
|
||||||
|
|
||||||
// Setup translations
|
// Setup translations
|
||||||
// let i18n = require('./libs/i18n');
|
// let i18n = require('./libs/api-v2/i18n');
|
||||||
|
|
||||||
const IS_PROD = nconf.get('IS_PROD');
|
const IS_PROD = nconf.get('IS_PROD');
|
||||||
// const IS_DEV = nconf.get('IS_DEV');
|
// const IS_DEV = nconf.get('IS_DEV');
|
||||||
@@ -42,7 +41,7 @@ let db = mongoose.connect(nconf.get('NODE_DB_URI'), mongooseOptions, (err) => {
|
|||||||
|
|
||||||
autoinc.init(db);
|
autoinc.init(db);
|
||||||
|
|
||||||
import './libs/firebase';
|
import './libs/api-v3/firebase';
|
||||||
|
|
||||||
// load schemas & models
|
// load schemas & models
|
||||||
import './models/challenge';
|
import './models/challenge';
|
||||||
@@ -84,6 +83,7 @@ app.set('port', nconf.get('PORT'));
|
|||||||
let oldApp = express(); // api v1 and v2, and not scoped routes
|
let oldApp = express(); // api v1 and v2, and not scoped routes
|
||||||
let newApp = express(); // api v3
|
let newApp = express(); // api v3
|
||||||
|
|
||||||
|
app.use(domainMiddleware(server, mongoose));
|
||||||
// Route requests to the right app
|
// Route requests to the right app
|
||||||
// Matches all request except the ones going to /api/v3/**
|
// Matches all request except the ones going to /api/v3/**
|
||||||
app.all(/^(?!\/api\/v3).+/i, oldApp);
|
app.all(/^(?!\/api\/v3).+/i, oldApp);
|
||||||
@@ -95,7 +95,7 @@ attachMiddlewares(newApp);
|
|||||||
|
|
||||||
/* OLD APP IS DISABLED UNTIL COMPATIBLE WITH NEW MODELS
|
/* OLD APP IS DISABLED UNTIL COMPATIBLE WITH NEW MODELS
|
||||||
//require('./middlewares/apiThrottle')(oldApp);
|
//require('./middlewares/apiThrottle')(oldApp);
|
||||||
oldApp.use(require('./middlewares/domain')(server,mongoose));
|
oldApp.use(require('./middlewares/api-v2/domain')(server,mongoose));
|
||||||
if (!IS_PROD && !DISABLE_LOGGING) oldApp.use(require('morgan')("dev"));
|
if (!IS_PROD && !DISABLE_LOGGING) oldApp.use(require('morgan')("dev"));
|
||||||
oldApp.use(require('compression')());
|
oldApp.use(require('compression')());
|
||||||
oldApp.set("views", __dirname + "/../views");
|
oldApp.set("views", __dirname + "/../views");
|
||||||
@@ -153,7 +153,7 @@ oldApp.use('/common/script/public', express['static'](publicDir + "/../../common
|
|||||||
oldApp.use('/common/img', express['static'](publicDir + "/../../common/img", { maxAge: maxAge }));
|
oldApp.use('/common/img', express['static'](publicDir + "/../../common/img", { maxAge: maxAge }));
|
||||||
oldApp.use(express['static'](publicDir));
|
oldApp.use(express['static'](publicDir));
|
||||||
|
|
||||||
oldApp.use(require('./middlewares/errorHandler'));
|
oldApp.use(require('./middlewares/api-v2/errorHandler'));
|
||||||
*/
|
*/
|
||||||
|
|
||||||
server.on('request', app);
|
server.on('request', app);
|
||||||
|
|||||||
Reference in New Issue
Block a user