diff --git a/.eslintignore b/.eslintignore index 5b7664ac76..2a43251c75 100644 --- a/.eslintignore +++ b/.eslintignore @@ -13,13 +13,6 @@ website/client/ # Temporarilly disabled. These should be removed when the linting errors are fixed common/script/content/index.js -common/script/public/**/*.js - -website/server/**/api-v2/**/*.js -website/server/routes/payments.js -website/server/routes/pages.js -website/server/middlewares/apiThrottle.js -website/server/middlewares/forceRefresh.js debug-scripts/* scripts/* diff --git a/Gruntfile.js b/Gruntfile.js index 8f716518fe..6b3fb3b2e6 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -131,7 +131,7 @@ module.exports = function(grunt) { grunt.registerTask('build:test', ['test:prepare:translations', 'build:dev']); grunt.registerTask('test:prepare:translations', function() { - var i18n = require('./website/server/libs/api-v3/i18n'), + var i18n = require('./website/server/libs/i18n'), fs = require('fs'); fs.writeFileSync('test/spec/mocks/translations.js', "if(!window.env) window.env = {};\n" + diff --git a/common/locales/en/settings.json b/common/locales/en/settings.json index e854bebd05..0fdfe59c7d 100644 --- a/common/locales/en/settings.json +++ b/common/locales/en/settings.json @@ -66,7 +66,6 @@ "deleteLocalAccountText": "Are you sure? This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If you're absolutely certain, type your password into the text box below.", "API": "API", "APIv3": "API v3", - "APIv2": "API v2 - Deprecated", "APIText": "Copy these for use in third party applications. However, think of your API Token like a password, and do not share it publicly. You may occasionally be asked for your User ID, but never post your API Token where others can see it, including on Github.", "APIToken": "API Token (this is a password - see warning above!)", "thirdPartyApps": "Third Party Apps", diff --git a/package.json b/package.json index 84e0b45773..151c6f8fcb 100644 --- a/package.json +++ b/package.json @@ -102,13 +102,10 @@ "scripts": { "lint": "eslint .", "test": "npm run lint && gulp test", - "test:api-v2:unit": "mocha test/server_side", - "test:api-v2:integration": "mocha test/api/v2 --recursive", "test:api-v3": "gulp test:api-v3", "test:api-v3:unit": "gulp test:api-v3:unit", "test:api-v3:integration": "gulp test:api-v3:integration", - "test:api-v3:integration:separate-server": "gulp test:api-v3:integration:separate-server", - "test:api-legacy": "istanbul cover -i \"website/server/**\" --dir coverage/api ./node_modules/mocha/bin/_mocha test/api-legacy", + "test:api-v3:integration:separate-server": "NODE_ENV=test gulp test:api-v3:integration:separate-server", "test:sanity": "mocha test/sanity --recursive", "test:common": "mocha test/common --recursive", "test:content": "mocha test/content --recursive", diff --git a/tasks/gulp-console.js b/tasks/gulp-console.js index 026d646cee..43c42abc8e 100644 --- a/tasks/gulp-console.js +++ b/tasks/gulp-console.js @@ -1,6 +1,6 @@ import mongoose from 'mongoose'; import autoinc from 'mongoose-id-autoinc'; -import logger from '../website/server/libs/api-v3/logger'; +import logger from '../website/server/libs/logger'; import nconf from 'nconf'; import repl from 'repl'; import gulp from 'gulp'; diff --git a/tasks/gulp-tests.js b/tasks/gulp-tests.js index 56eba559aa..e7dd6e01f0 100644 --- a/tasks/gulp-tests.js +++ b/tasks/gulp-tests.js @@ -21,16 +21,12 @@ let server; const TEST_DB_URI = nconf.get('TEST_DB_URI'); -const API_V2_TEST_COMMAND = 'npm run test:api-v2:integration'; const API_V3_TEST_COMMAND = 'npm run test:api-v3'; -const LEGACY_API_TEST_COMMAND = 'npm run test:api-legacy'; const SANITY_TEST_COMMAND = 'npm run test:sanity'; const COMMON_TEST_COMMAND = 'npm run test:common'; const CONTENT_TEST_COMMAND = 'npm run test:content'; const CONTENT_OPTIONS = {maxBuffer: 1024 * 500}; const KARMA_TEST_COMMAND = 'npm run test:karma'; -const SERVER_SIDE_TEST_COMMAND = 'npm run test:api-v2:unit'; -const ISTANBUL_TEST_COMMAND = 'npm run test:api-legacy'; /* Helper methods for reporting test summary */ let testResults = []; @@ -196,43 +192,6 @@ gulp.task('test:server_side:safe', ['test:prepare:build'], (cb) => { pipe(runner); }); -gulp.task('test:api-legacy', ['test:prepare:mongo'], (cb) => { - let runner = exec( - testBin(ISTANBUL_TEST_COMMAND), - (err, stdout, stderr) => { - cb(err); - } - ); - pipe(runner); -}); - -gulp.task('test:api-legacy:safe', ['test:prepare:mongo'], (cb) => { - let runner = exec( - testBin(ISTANBUL_TEST_COMMAND), - (err, stdout, stderr) => { - testResults.push({ - suite: 'API (legacy) Specs', - pass: testCount(stdout, /(\d+) passing/), - fail: testCount(stdout, /(\d+) failing/), - pend: testCount(stdout, /(\d+) pending/) - }); - cb(); - } - ); - pipe(runner); -}); - -gulp.task('test:api-legacy:clean', (cb) => { - pipe(exec(testBin(LEGACY_API_TEST_COMMAND), () => cb())); -}); - -gulp.task('test:api-legacy:watch', [ - 'test:prepare:mongo', - 'test:api-legacy:clean' -], () => { - gulp.watch(['website/server/**', 'test/api-legacy/**'], ['test:api-legacy:clean']); -}); - gulp.task('test:karma', ['test:prepare:build'], (cb) => { let runner = exec( testBin(KARMA_TEST_COMMAND), @@ -320,46 +279,6 @@ gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => { }); }); -/*gulp.task('test:api-v2', ['test:prepare:server'], (done) => { - process.env.API_VERSION = 'v2'; - awaitPort(TEST_SERVER_PORT).then(() => { - runMochaTests('./test/api/v2/**//*.js', server, done) - }); -}); - -gulp.task('test:api-v2:watch', ['test:prepare:server'], () => { - process.env.RUN_INTEGRATION_TEST_FOREVER = true; - gulp.watch(['website/server/**', 'test/api/v2/**'], ['test:api-v2']); -}); - -gulp.task('test:api-v2:safe', ['test:prepare:server'], (done) => { - awaitPort(TEST_SERVER_PORT).then(() => { - let runner = exec( - testBin(API_V2_TEST_COMMAND), - (err, stdout, stderr) => { - testResults.push({ - suite: 'API V2 Specs\t', - pass: testCount(stdout, /(\d+) passing/), - fail: testCount(stderr, /(\d+) failing/), - pend: testCount(stdout, /(\d+) pending/) - }); - done(); - } - ); - pipe(runner); - }); -});*/ - -gulp.task('test:api-v2:integration', (done) => { - let runner = exec( - testBin('mocha test/api/v2 --recursive'), - {maxBuffer: 500*1024}, - (err, stdout, stderr) => done(err) - ) - - pipe(runner); -}); - gulp.task('test:api-v3:unit', (done) => { let runner = exec( testBin('mocha test/api/v3/unit --recursive'), @@ -370,7 +289,7 @@ gulp.task('test:api-v3:unit', (done) => { }); gulp.task('test:api-v3:unit:watch', () => { - gulp.watch(['website/server/libs/api-v3/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], ['test:api-v3:unit']); + gulp.watch(['website/server/libs/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], ['test:api-v3:unit']); }); gulp.task('test:api-v3:integration', (done) => { @@ -384,7 +303,7 @@ gulp.task('test:api-v3:integration', (done) => { }); gulp.task('test:api-v3:integration:watch', () => { - gulp.watch(['website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/api-v3/*.js', + gulp.watch(['website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js', 'test/api/v3/integration/**/*'], ['test:api-v3:integration']); }); @@ -405,7 +324,6 @@ gulp.task('test', (done) => { 'test:karma', 'test:api-v3:unit', 'test:api-v3:integration', - 'test:api-v2:integration', done ); }); @@ -416,100 +334,4 @@ gulp.task('test:api-v3', (done) => { 'test:api-v3:integration', done ); -}); - -// Old tests tasks -/* -gulp.task('test:api-v3', ['test:api-v3:unit', 'test:api-v3:integration']); - -gulp.task('test:api-v3:watch', ['test:api-v3:unit:watch', 'test:api-v3:integration:watch']); - -gulp.task('test:api-v3:unit', (done) => {*/ -// runMochaTests('./test/api/v3/unit/**/*.js', null, done) -/*}); - -gulp.task('test:api-v3:unit:watch', () => { - gulp.watch(['website/server/**', 'test/api/v3/unit/**'], ['test:api-v3:unit']); -}); - -gulp.task('test:api-v3:integration', ['test:prepare:server'], (done) => { - process.env.API_VERSION = 'v3'; - awaitPort(TEST_SERVER_PORT).then(() => {*/ -// runMochaTests('./test/api/v3/integration/**/*.js', server, done) -/* }); -}); - -gulp.task('test:api-v3:integration:watch', ['test:prepare:server'], () => { - process.env.RUN_INTEGRATION_TEST_FOREVER = true; - gulp.watch(['website/server/**', 'test/api/v3/integration/**'], ['test:api-v3:integration']); -}); - -gulp.task('test:api-v3:safe', ['test:prepare:server'], (done) => { - awaitPort(TEST_SERVER_PORT).then(() => { - let runner = exec( - testBin(API_V3_TEST_COMMAND), - (err, stdout, stderr) => { - testResults.push({ - suite: 'API V3 Specs\t', - pass: testCount(stdout, /(\d+) passing/), - fail: testCount(stdout, /(\d+) failing/), - pend: testCount(stdout, /(\d+) pending/) - }); - done(); - } - ); - pipe(runner); - }); -}); - -gulp.task('test:all', (done) => { - runSequence( - //'test:e2e:safe', - //'test:common:safe', - //'test:content:safe', - // 'test:server_side:safe', - //'test:karma:safe', - //'test:api-legacy:safe', - //'test:api-v2:safe', - 'test:api-v3:safe', - done); -}); - -gulp.task('test', ['test:all'], () => { - let totals = [0,0,0]; - - console.log('\n\x1b[36m\x1b[4mHabitica Test Summary\x1b[0m\n'); - testResults.forEach((s) => { - totals[0] = totals[0] + s.pass; - totals[1] = totals[1] + s.fail; - totals[2] = totals[2] + s.pend; - console.log( - `\x1b[33m\x1b[4m${s.suite}\x1b[0m\t`, - `\x1b[32mPassing: ${s.pass},\t`, - `\x1b[31mFailed: ${s.fail},\t`, - `\x1b[36mPending: ${s.pend}\t` - ); - - if (s.pass === 0) { - console.error('ERROR: Detected a test suite with 0 passing tests. Something may be wrong causing the build to error.'); - process.exit(1); - } - }); - - console.log( - '\n\x1b[33m\x1b[4mTotal:\x1b[0m\t\t\t', - `\x1b[32mPassing: ${totals[0]},\t`, - `\x1b[31mFailed: ${totals[1]},\t`, - `\x1b[36mPending: ${totals[2]}\t` - ); - - kill(server); - - if (totals[1] > 0) { - console.error('ERROR: There are failing tests!'); - process.exit(1); - } else { - console.log('\n\x1b[36mThanks for helping keep Habitica clean!\x1b[0m'); - process.exit(); - } -});*/ +}); \ No newline at end of file diff --git a/test/api-legacy/README.md b/test/api-legacy/README.md deleted file mode 100644 index 9157af554d..0000000000 --- a/test/api-legacy/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Legacy API Tests - -These tests have been deprecated. Any tests that you see in this folder should be converted to ES2015 and ported to the `/test/api` directory. - -Once all tests have been ported, this directory will be removed. diff --git a/test/api-legacy/api-helper.js b/test/api-legacy/api-helper.js deleted file mode 100644 index 1b994a80a8..0000000000 --- a/test/api-legacy/api-helper.js +++ /dev/null @@ -1,92 +0,0 @@ -require('babel-core/register'); -var path, superagentDefaults; - -superagentDefaults = require("superagent-defaults"); - -global.request = superagentDefaults(); - -global.mongoose = require("mongoose"); -var Bluebird = require('bluebird'); -mongoose.Promise = Bluebird; - -global.moment = require("moment"); - -global.async = require("async"); - -global._ = require("lodash"); - -global.shared = require("../../common"); - -global.User = require("../../website/server/models/user").model; - -global.chai = require("chai"); - -chai.use(require("sinon-chai")); - -global.expect = chai.expect; - -path = require("path"); - -global.conf = require("nconf"); - -conf.argv().env().file({ - file: path.join(__dirname, "../config.json") -}).defaults(); - -conf.set("PORT", "1337"); - -process.env.NODE_DB_URI = "mongodb://localhost/habitrpg_test_api_legacy"; - -global.baseURL = "http://localhost:" + conf.get("PORT") + "/api/v2"; - -global.user = void 0; - -global.expectCode = function(res, code) { - if (code === 200) { - expect(res.body.err).to.not.exist; - } - return expect(res.statusCode).to.equal(code); -}; - -global.registerNewUser = function(cb, main) { - var password, randomID, username; - if (main == null) { - main = true; - } - randomID = shared.uuid(); - if (main) { - username = password = randomID; - } - return request.post(baseURL + "/register").set("Accept", "application/json").set("X-API-User", null).set("X-API-Key", null).send({ - username: randomID, - password: randomID, - confirmPassword: randomID, - email: randomID + "@gmail.com" - }).end(function(err, res) { - var _id, apiToken; - if (!main) { - return cb(null, res.body); - } - _id = res.body._id; - apiToken = res.body.apiToken; - return User.findOne({ - _id: _id, - apiToken: apiToken - }, function(err, _user) { - expect(err).to.not.be.ok; - global.user = _user; - request.set("Accept", "application/json").set("X-API-User", _id).set("X-API-Key", apiToken); - return cb(null, res.body); - }); - }); -}; - -global.registerManyUsers = function(number, callback) { - return async.times(number, function(n, next) { - return registerNewUser(function(err, user) { - return next(err, user); - }, false); - }, function(err, users) { - return callback(err, users); - }); -}; diff --git a/test/api-legacy/challenges.js b/test/api-legacy/challenges.js deleted file mode 100644 index b09754bc5c..0000000000 --- a/test/api-legacy/challenges.js +++ /dev/null @@ -1,412 +0,0 @@ -var Challenge, Group, app; - -app = require("../../website/server/server"); - -Group = require("../../website/server/models/group").model; - -Challenge = require("../../website/server/models/challenge").model; - -describe("Challenges", function() { - var challenge, group, updateTodo; - challenge = void 0; - updateTodo = void 0; - group = void 0; - beforeEach(function(done) { - return async.waterfall([ - function(cb) { - return registerNewUser(cb, true); - }, function(user, cb) { - return request.post(baseURL + "/groups").send({ - name: "TestGroup", - type: "party" - }).end(function(err, res) { - expectCode(res, 200); - group = res.body; - expect(group.members.length).to.equal(1); - expect(group.leader).to.equal(user._id); - return cb(); - }); - }, function(cb) { - return request.post(baseURL + "/challenges").send({ - group: group._id, - dailys: [ - { - type: "daily", - text: "Challenge Daily" - } - ], - todos: [ - { - type: "todo", - text: "Challenge Todo 1", - notes: "Challenge Notes" - } - ], - rewards: [], - habits: [] - }).end(function(err, res) { - challenge = res.body; - return done(); - }); - } - ]); - }); - describe('POST /challenge', function() { - return it("Creates a challenge", function(done) { - return request.post(baseURL + "/challenges").send({ - group: group._id, - dailys: [ - { - type: "daily", - text: "Challenge Daily" - } - ], - todos: [ - { - type: "todo", - text: "Challenge Todo 1", - notes: "Challenge Notes" - }, { - type: "todo", - text: "Challenge Todo 2", - notes: "Challenge Notes" - } - ], - rewards: [], - habits: [], - official: true - }).end(function(err, res) { - expectCode(res, 200); - return async.parallel([ - function(cb) { - return User.findById(user._id, cb); - }, function(cb) { - return Challenge.findById(res.body._id, cb); - } - ], function(err, results) { - var user; - user = results[0]; - challenge = results[1]; - expect(user.dailys[user.dailys.length - 1].text).to.equal("Challenge Daily"); - expect(challenge.official).to.equal(false); - return done(); - }); - }); - }); - }); - describe('POST /challenge/:cid', function() { - it("updates the notes on user's version of a challenge task's note without updating the challenge", function(done) { - updateTodo = challenge.todos[0]; - updateTodo.notes = "User overriden notes"; - return async.waterfall([ - function(cb) { - return request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end(function(err, res) { - return cb(); - }); - }, function(cb) { - return Challenge.findById(challenge._id, cb); - }, function(chal, cb) { - expect(chal.todos[0].notes).to.eql("Challenge Notes"); - return cb(); - }, function(cb) { - return request.get(baseURL + "/user/tasks/" + updateTodo.id).end(function(err, res) { - expect(res.body.notes).to.eql("User overriden notes"); - return done(); - }); - } - ]); - }); - it("changes user's copy of challenge tasks when the challenge is updated", function(done) { - challenge.dailys[0].text = "Updated Daily"; - return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { - challenge = res.body; - expect(challenge.dailys[0].text).to.equal("Updated Daily"); - return User.findById(user._id, function(err, _user) { - expectCode(res, 200); - expect(_user.dailys[_user.dailys.length - 1].text).to.equal("Updated Daily"); - return done(); - }); - }); - }); - it("does not changes user's notes on tasks when challenge task notes are updated", function(done) { - challenge.todos[0].notes = "Challenge Updated Todo Notes"; - return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { - challenge = res.body; - expect(challenge.todos[0].notes).to.equal("Challenge Updated Todo Notes"); - return User.findById(user._id, function(err, _user) { - expectCode(res, 200); - expect(_user.todos[_user.todos.length - 1].notes).to.equal("Challenge Notes"); - return done(); - }); - }); - }); - return it("shows user notes on challenge page", function(done) { - updateTodo = challenge.todos[0]; - updateTodo.notes = "User overriden notes"; - return async.waterfall([ - function(cb) { - return request.put(baseURL + "/user/tasks/" + updateTodo.id).send(updateTodo).end(function(err, res) { - return cb(); - }); - }, function(cb) { - return Challenge.findById(challenge._id, cb); - }, function(chal, cb) { - expect(chal.todos[0].notes).to.eql("Challenge Notes"); - return cb(); - }, function(cb) { - return request.get(baseURL + "/challenges/" + challenge._id + "/member/" + user._id).end(function(err, res) { - expect(res.body.todos[res.body.todos.length - 1].notes).to.equal("User overriden notes"); - return done(); - }); - } - ]); - }); - }); - it("Complete To-Dos", function(done) { - return User.findById(user._id, function(err, _user) { - var numTasks, u; - u = _user; - numTasks = _.size(u.todos); - return request.post(baseURL + "/user/tasks/" + u.todos[0].id + "/up").end(function(err, res) { - return request.post(baseURL + "/user/tasks/clear-completed").end(function(err, res) { - expect(_.size(res.body)).to.equal(numTasks - 1); - return done(); - }); - }); - }); - }); - it("Challenge deleted, breaks task link", function(done) { - var itThis; - itThis = this; - return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { - return User.findById(user._id, function(err, user) { - var daily, len, unset; - len = user.dailys.length - 1; - daily = user.dailys[user.dailys.length - 1]; - expect(daily.challenge.broken).to.equal("CHALLENGE_DELETED"); - unset = { - $unset: {} - }; - unset["$unset"]["dailys." + len + ".challenge.broken"] = 1; - return User.findByIdAndUpdate(user._id, unset, { - "new": true - }, function(err, user) { - expect(err).to.not.exist; - expect(user.dailys[len].challenge.broken).to.not.exist; - return request.post(baseURL + "/user/tasks/" + daily.id + "/up").end(function(err, res) { - return setTimeout((function() { - return User.findById(user._id, function(err, user) { - expect(user.dailys[len].challenge.broken).to.equal("CHALLENGE_DELETED"); - return done(); - }); - }), 100); - }); - }); - }); - }); - }); - it("admin creates a challenge", function(done) { - return User.findByIdAndUpdate(user._id, { - $set: { - "contributor.admin": true - } - }, { - "new": true - }, function(err, _user) { - expect(err).to.not.exist; - return async.parallel([ - function(cb) { - return request.post(baseURL + "/challenges").send({ - group: group._id, - dailys: [], - todos: [], - rewards: [], - habits: [], - official: false - }).end(function(err, res) { - expect(res.body.official).to.equal(false); - return cb(); - }); - }, function(cb) { - return request.post(baseURL + "/challenges").send({ - group: group._id, - dailys: [], - todos: [], - rewards: [], - habits: [], - official: true - }).end(function(err, res) { - expect(res.body.official).to.equal(true); - return cb(); - }); - } - ], done); - }); - }); - it("User creates a non-tavern challenge with prize, deletes it, gets refund", function(done) { - return User.findByIdAndUpdate(user._id, { - $set: { - "balance": 8 - } - }, { - "new": true - }, function(err, user) { - expect(err).to.not.be.ok; - return request.post(baseURL + "/challenges").send({ - group: group._id, - dailys: [], - todos: [], - rewards: [], - habits: [], - prize: 10 - }).end(function(err, res) { - expect(res.body.prize).to.equal(10); - return async.parallel([ - function(cb) { - return User.findById(user._id, cb); - }, function(cb) { - return Challenge.findById(res.body._id, cb); - } - ], function(err, results) { - user = results[0]; - challenge = results[1]; - expect(user.balance).to.equal(5.5); - return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { - return User.findById(user._id, function(err, _user) { - expect(_user.balance).to.equal(8); - return done(); - }); - }); - }); - }); - }); - }); - it("User creates a tavern challenge with prize, deletes it, and does not get refund", function(done) { - return User.findByIdAndUpdate(user._id, { - $set: { - "balance": 8 - } - }, { - "new": true - }, function(err, user) { - expect(err).to.not.be.ok; - return request.post(baseURL + "/challenges").send({ - group: 'habitrpg', - dailys: [], - todos: [], - rewards: [], - habits: [], - prize: 10 - }).end(function(err, res) { - expect(res.body.prize).to.equal(10); - return async.parallel([ - function(cb) { - return User.findById(user._id, cb); - }, function(cb) { - return Challenge.findById(res.body._id, cb); - } - ], function(err, results) { - user = results[0]; - challenge = results[1]; - expect(user.balance).to.equal(5.5); - return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { - return User.findById(user._id, function(err, _user) { - expect(_user.balance).to.equal(5.5); - return done(); - }); - }); - }); - }); - }); - }); - return describe("non-owner permissions", function() { - challenge = void 0; - beforeEach(function(done) { - return async.waterfall([ - function(cb) { - return request.post(baseURL + "/challenges").send({ - group: group._id, - name: 'challenge name', - dailys: [ - { - type: "daily", - text: "Challenge Daily" - } - ] - }).end(function(err, res) { - challenge = res.body; - return cb(); - }); - }, function(cb) { - return registerNewUser(done, true); - } - ]); - }); - context("non-owner", function() { - it('can not edit challenge', function(done) { - challenge.name = 'foobar'; - return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { - var error; - error = res.body.err; - expect(error).to.eql("You don't have permissions to edit this challenge"); - return done(); - }); - }); - it('can not close challenge', function(done) { - return request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id).end(function(err, res) { - var error; - error = res.body.err; - expect(error).to.eql("You don't have permissions to close this challenge"); - return done(); - }); - }); - return it('can not delete challenge', function(done) { - return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { - var error; - error = res.body.err; - expect(error).to.eql("You don't have permissions to delete this challenge"); - return done(); - }); - }); - }); - return context("non-owner that is an admin", function() { - beforeEach(function(done) { - return User.findByIdAndUpdate(user._id, { - 'contributor.admin': true - }, { - "new": true - }, done); - }); - it('can edit challenge', function(done) { - challenge.name = 'foobar'; - return request.post(baseURL + "/challenges/" + challenge._id).send(challenge).end(function(err, res) { - expect(res.body.err).to.not.exist; - return Challenge.findById(challenge._id, function(err, chal) { - expect(chal.name).to.eql('foobar'); - return done(); - }); - }); - }); - it('can close challenge', function(done) { - return request.post(baseURL + "/challenges/" + challenge._id + "/close?uid=" + user._id).end(function(err, res) { - expect(res.body.err).to.not.exist; - return User.findById(user._id, function(err, usr) { - expect(usr.achievements.challenges[0]).to.eql(challenge.name); - return done(); - }); - }); - }); - return it('can delete challenge', function(done) { - return request.del(baseURL + "/challenges/" + challenge._id).end(function(err, res) { - expect(res.body.err).to.not.exist; - return request.get(baseURL + "/challenges/" + challenge._id).end(function(err, res) { - var error; - error = res.body.err; - expect(error).to.eql("Challenge " + challenge._id + " not found"); - return done(); - }); - }); - }); - }); - }); -}); diff --git a/test/api-legacy/chat.js b/test/api-legacy/chat.js deleted file mode 100644 index 1f2dbab487..0000000000 --- a/test/api-legacy/chat.js +++ /dev/null @@ -1,71 +0,0 @@ -var Group, app, diff; - -diff = require("deep-diff"); - -Group = require("../../website/server/models/group").model; - -app = require("../../website/server/server"); - -describe("Chat", function() { - var chat, group; - group = void 0; - before(function(done) { - return async.waterfall([ - function(cb) { - return registerNewUser(cb, true); - }, function(user, cb) { - return request.post(baseURL + "/groups").send({ - name: "TestGroup", - type: "party" - }).end(function(err, res) { - expectCode(res, 200); - group = res.body; - expect(group.members.length).to.equal(1); - expect(group.leader).to.equal(user._id); - return cb(); - }); - } - ], done); - }); - chat = void 0; - return it("removes a user's chat notifications when user is kicked", function(done) { - var userToRemove; - userToRemove = null; - return async.waterfall([ - function(cb) { - return registerManyUsers(1, cb); - }, function(members, cb) { - userToRemove = members[0]; - return request.post(baseURL + "/groups/" + group._id + "/invite").send({ - uuids: [userToRemove._id] - }).end(function() { - return cb(); - }); - }, function(cb) { - return request.post(baseURL + "/groups/" + group._id + "/join").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) { - return cb(); - }); - }, function(cb) { - var msg; - msg = "TestMsg"; - return request.post(baseURL + "/groups/" + group._id + "/chat?message=" + msg).end(function(err, res) { - return cb(); - }); - }, function(cb) { - return request.get(baseURL + "/user").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) { - expect(res.body.newMessages[group._id]).to.exist; - return cb(); - }); - }, function(cb) { - return request.post(baseURL + "/groups/" + group._id + "/removeMember?uuid=" + userToRemove._id).end(function(err, res) { - return cb(); - }); - }, function(cb) { - return request.get(baseURL + "/user").set("X-API-User", userToRemove._id).set("X-API-Key", userToRemove.apiToken).end(function(err, res) { - expect(res.body.newMessages[group._id]).to.not.exist; - return cb(); - }); - } - ], done); - }); -}); diff --git a/test/api-legacy/coupons.js b/test/api-legacy/coupons.js deleted file mode 100644 index 4d4e366473..0000000000 --- a/test/api-legacy/coupons.js +++ /dev/null @@ -1,221 +0,0 @@ -var Coupon, app, makeSudoUser; - -app = require("../../website/server/server"); - -Coupon = require("../../website/server/models/coupon").model; - -makeSudoUser = function(usr, cb) { - return registerNewUser(function() { - var sudoUpdate; - sudoUpdate = { - "$set": { - "contributor.sudo": true - } - }; - return User.findByIdAndUpdate(user._id, sudoUpdate, { - "new": true - }, function(err, _user) { - usr = _user; - return cb(); - }); - }, true); -}; - -describe("Coupons", function() { - var coupons; - before(function(done) { - return async.parallel([ - function(cb) { - return mongoose.connection.collections['coupons'].drop(function(err) { - return cb(); - }); - }, function(cb) { - return mongoose.connection.collections['users'].drop(function(err) { - return cb(); - }); - } - ], done); - }); - coupons = null; - describe("POST /api/v2/coupons/generate/:event", function() { - context("while sudo user", function() { - before(function(done) { - makeSudoUser(user, done); - }); - - it("generates coupons", function(done) { - var queries; - queries = '?count=10'; - - request.post(baseURL + '/coupons/generate/wondercon' + queries).end(function(err, res) { - expectCode(res, 200); - Coupon.find({ - event: 'wondercon' - }, function(err, _coupons) { - coupons = _coupons; - expect(coupons.length).to.equal(10); - _(coupons).each(function(c) { - expect(c.event).to.equal('wondercon'); - }).value(); - done(); - }); - }); - }); - }); - - return context("while regular user", function() { - before(function(done) { - return registerNewUser(done, true); - }); - return it("does not generate coupons", function(done) { - var queries; - queries = '?count=10'; - return request.post(baseURL + '/coupons/generate/wondercon' + queries).end(function(err, res) { - expectCode(res, 401); - expect(res.body.err).to.equal('You don\'t have admin access'); - return done(); - }); - }); - }); - }); - describe("GET /api/v2/coupons", function() { - context("while sudo user", function() { - before(function(done) { - return makeSudoUser(user, done); - }); - it("gets coupons", function(done) { - var queries; - queries = '?_id=' + user._id + '&apiToken=' + user.apiToken; - return request.get(baseURL + '/coupons' + queries).end(function(err, res) { - var codes; - expectCode(res, 200); - codes = res.text; - expect(codes).to.contain('code'); - _(coupons).each(function(c) { - return expect(codes).to.contain(c._id); - }).value(); - return done(); - }); - }); - it("gets first 5 coupons out of 10 when a limit of 5 is set", function(done) { - var queries; - queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&limit=5'; - return request.get(baseURL + '/coupons' + queries).end(function(err, res) { - var codes, firstHalf, secondHalf, sortedCoupons; - expectCode(res, 200); - codes = res.text; - sortedCoupons = _.sortBy(coupons, 'seq'); - firstHalf = sortedCoupons.slice(0, 5); - secondHalf = sortedCoupons.slice(5, 10); - _(firstHalf).each(function(c) { - return expect(codes).to.contain(c._id); - }).value(); - _(secondHalf).each(function(c) { - return expect(codes).to.not.contain(c._id); - }).value(); - return done(); - }); - }); - return it("gets last 5 coupons out of 10 when a limit of 5 is set", function(done) { - var queries; - queries = '?_id=' + user._id + '&apiToken=' + user.apiToken + '&skip=5'; - return request.get(baseURL + '/coupons' + queries).end(function(err, res) { - var codes, firstHalf, secondHalf, sortedCoupons; - expectCode(res, 200); - codes = res.text; - sortedCoupons = _.sortBy(coupons, 'seq'); - firstHalf = sortedCoupons.slice(0, 5); - secondHalf = sortedCoupons.slice(5, 10); - _(firstHalf).each(function(c) { - return expect(codes).to.not.contain(c._id); - }).value(); - _(secondHalf).each(function(c) { - return expect(codes).to.contain(c._id); - }).value(); - return done(); - }); - }); - }); - return context("while regular user", function() { - before(function(done) { - return registerNewUser(done, true); - }); - return it("does not get coupons", function(done) { - var queries; - queries = '?_id=' + user._id + '&apiToken=' + user.apiToken; - return request.get(baseURL + '/coupons' + queries).end(function(err, res) { - expectCode(res, 401); - expect(res.body.err).to.equal('You don\'t have admin access'); - return done(); - }); - }); - }); - }); - return describe("POST /api/v2/user/coupon/:code", function() { - var specialGear; - specialGear = function(gear, has) { - var items; - items = ['body_special_wondercon_gold', 'body_special_wondercon_black', 'body_special_wondercon_red', 'back_special_wondercon_red', 'back_special_wondercon_black', 'back_special_wondercon_red', 'eyewear_special_wondercon_black', 'eyewear_special_wondercon_red']; - return _(items).each(function(i) { - if (has) { - return expect(gear[i]).to.exist; - } else { - return expect(gear[i]).to.not.exist; - } - }).value(); - }; - beforeEach(function(done) { - return registerNewUser(function() { - var gear; - gear = user.items.gear.owned; - specialGear(gear, false); - return done(); - }, true); - }); - context("unused coupon", function() { - return it("applies coupon and awards equipment", function(done) { - var code; - code = coupons[0]._id; - return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) { - var gear; - expectCode(res, 200); - gear = res.body.items.gear.owned; - specialGear(gear, true); - return done(); - }); - }); - }); - context("already used coupon", function() { - return it("does not apply coupon and does not award equipment", function(done) { - var code; - code = coupons[0]._id; - return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) { - expectCode(res, 400); - expect(res.body.err).to.equal("Coupon already used"); - return User.findById(user._id, function(err, _user) { - var gear; - gear = _user.items.gear.owned; - specialGear(gear, false); - return done(); - }); - }); - }); - }); - return context("invalid coupon", function() { - return it("does not apply coupon and does not award equipment", function(done) { - var code; - code = "not-a-real-coupon"; - return request.post(baseURL + '/user/coupon/' + code).end(function(err, res) { - expectCode(res, 400); - expect(res.body.err).to.equal("Invalid coupon code"); - return User.findById(user._id, function(err, _user) { - var gear; - gear = _user.items.gear.owned; - specialGear(gear, false); - return done(); - }); - }); - }); - }); - }); -}); diff --git a/test/api-legacy/inAppPurchases.js b/test/api-legacy/inAppPurchases.js deleted file mode 100644 index 96809a7717..0000000000 --- a/test/api-legacy/inAppPurchases.js +++ /dev/null @@ -1,367 +0,0 @@ -var app, iapMock, inApp, rewire, sinon; - -app = require('../../website/server/server'); - -rewire = require('rewire'); - -sinon = require('sinon'); - -inApp = rewire('../../website/server/controllers/payments/iap'); - -iapMock = {}; - -inApp.__set__('iap', iapMock); - -describe('In-App Purchases', function() { - describe('Android', function() { - var next, paymentSpy, req, res; - req = { - body: { - transaction: { - reciept: 'foo', - signature: 'sig' - } - } - }; - res = { - locals: { - user: { - _id: 'user' - } - }, - json: sinon.spy() - }; - next = function() { - return true; - }; - paymentSpy = sinon.spy(); - before(function() { - return inApp.__set__('payments.buyGems', paymentSpy); - }); - afterEach(function() { - paymentSpy.reset(); - return res.json.reset(); - }); - context('successful app purchase', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - iapMock.validate = function(iapGoogle, iapBodyReciept, cb) { - return cb(null, true); - }; - iapMock.isValidated = function(googleRes) { - return googleRes; - }; - return iapMock.GOOGLE = 'google'; - }); - it('calls res.json with succesful result object', function() { - var expectedResObj; - expectedResObj = { - ok: true, - data: true - }; - inApp.androidVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('calls payments.buyGems function', function() { - inApp.androidVerify(req, res, next); - expect(paymentSpy).to.be.calledOnce; - return expect(paymentSpy).to.be.calledWith({ - user: res.locals.user, - paymentMethod: 'IAP GooglePlay', - amount: 5.25 - }); - }); - }); - context('error in setup', function() { - before(function() { - return iapMock.setup = function(cb) { - return cb("error in setup"); - }; - }); - it('calls res.json with setup error object', function() { - var expectedResObj; - expectedResObj = { - ok: false, - data: 'IAP Error' - }; - inApp.androidVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.androidVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); - }); - context('error in validation', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - return iapMock.validate = function(iapGoogle, iapBodyReciept, cb) { - return cb('error in validation', true); - }; - }); - it('calls res.json with validation error object', function() { - var expectedResObj; - expectedResObj = { - ok: false, - data: { - code: 6778001, - message: 'error in validation' - } - }; - inApp.androidVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.androidVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); - }); - return context('iap is not valid', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - iapMock.validate = function(iapGoogle, iapBodyReciept, cb) { - return cb(null, false); - }; - return iapMock.isValidated = function(googleRes) { - return googleRes; - }; - }); - it('does not call res.json', function() { - inApp.androidVerify(req, res, next); - return expect(res.json).to.not.be.called; - }); - return it('does not calls payments.buyGems function', function() { - inApp.androidVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); - }); - }); - return describe('iOS', function() { - var next, paymentSpy, req, res; - req = { - body: { - transaction: { - reciept: 'foo' - } - } - }; - res = { - locals: { - user: { - _id: 'user' - } - }, - json: sinon.spy() - }; - next = function() { - return true; - }; - paymentSpy = sinon.spy(); - before(function() { - return inApp.__set__('payments.buyGems', paymentSpy); - }); - afterEach(function() { - paymentSpy.reset(); - return res.json.reset(); - }); - context('successful app purchase', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - iapMock.validate = function(iapApple, iapBodyReciept, cb) { - return cb(null, true); - }; - iapMock.isValidated = function(appleRes) { - return appleRes; - }; - iapMock.getPurchaseData = function(appleRes) { - return [ - { - productId: 'com.habitrpg.ios.Habitica.20gems' - } - ]; - }; - return iapMock.APPLE = 'apple'; - }); - it('calls res.json with succesful result object', function() { - var expectedResObj; - expectedResObj = { - ok: true, - data: true - }; - inApp.iosVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('calls payments.buyGems function', function() { - inApp.iosVerify(req, res, next); - expect(paymentSpy).to.be.calledOnce; - return expect(paymentSpy).to.be.calledWith({ - user: res.locals.user, - paymentMethod: 'IAP AppleStore', - amount: 5.25 - }); - }); - }); - context('error in setup', function() { - before(function() { - return iapMock.setup = function(cb) { - return cb("error in setup"); - }; - }); - it('calls res.json with setup error object', function() { - var expectedResObj; - expectedResObj = { - ok: false, - data: 'IAP Error' - }; - inApp.iosVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.iosVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); - }); - context('error in validation', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - return iapMock.validate = function(iapApple, iapBodyReciept, cb) { - return cb('error in validation', true); - }; - }); - it('calls res.json with validation error object', function() { - var expectedResObj; - expectedResObj = { - ok: false, - data: { - code: 6778001, - message: 'error in validation' - } - }; - inApp.iosVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.iosVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); - }); - context('iap is not valid', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - iapMock.validate = function(iapApple, iapBodyReciept, cb) { - return cb(null, false); - }; - return iapMock.isValidated = function(appleRes) { - return appleRes; - }; - }); - it('does not call res.json', function() { - var expectedResObj; - inApp.iosVerify(req, res, next); - expectedResObj = { - ok: false, - data: { - code: 6778001, - message: 'Invalid receipt' - } - }; - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.iosVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); - }); - context('iap is valid but has no purchaseDataList', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - iapMock.validate = function(iapApple, iapBodyReciept, cb) { - return cb(null, true); - }; - iapMock.isValidated = function(appleRes) { - return appleRes; - }; - iapMock.getPurchaseData = function(appleRes) { - return []; - }; - return iapMock.APPLE = 'apple'; - }); - it('calls res.json with succesful result object', function() { - var expectedResObj; - expectedResObj = { - ok: false, - data: { - code: 6778001, - message: 'Incorrect receipt content' - } - }; - inApp.iosVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.iosVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); - }); - return context('iap is valid, has purchaseDataList, but productId does not match', function() { - before(function() { - iapMock.setup = function(cb) { - return cb(null); - }; - iapMock.validate = function(iapApple, iapBodyReciept, cb) { - return cb(null, true); - }; - iapMock.isValidated = function(appleRes) { - return appleRes; - }; - iapMock.getPurchaseData = function(appleRes) { - return [ - { - productId: 'com.another.company' - } - ]; - }; - return iapMock.APPLE = 'apple'; - }); - it('calls res.json with incorrect reciept obj', function() { - var expectedResObj; - expectedResObj = { - ok: false, - data: { - code: 6778001, - message: 'Incorrect receipt content' - } - }; - inApp.iosVerify(req, res, next); - expect(res.json).to.be.calledOnce; - return expect(res.json).to.be.calledWith(expectedResObj); - }); - return it('does not calls payments.buyGems function', function() { - inApp.iosVerify(req, res, next); - return expect(paymentSpy).to.not.be.called; - }); - }); - }); -}); diff --git a/test/api-legacy/party.js b/test/api-legacy/party.js deleted file mode 100644 index 2f98b67bde..0000000000 --- a/test/api-legacy/party.js +++ /dev/null @@ -1,372 +0,0 @@ -var Group, app, diff; - -diff = require("deep-diff"); - -Group = require("../../website/server/models/group").model; - -app = require("../../website/server/server"); - -describe("Party", function() { - return context("Quests", function() { - var group, notParticipating, participating, party; - party = void 0; - group = void 0; - participating = []; - notParticipating = []; - beforeEach(function(done) { - Group.update({ - _id: "habitrpg" - }, { - $set: { - quest: { - key: "dilatory", - active: true, - progress: { - hp: shared.content.quests.dilatory.boss.hp, - rage: 0 - } - } - } - }).exec(); - return async.waterfall([ - function(cb) { - return registerNewUser(cb, true); - }, function(user, cb) { - return request.post(baseURL + "/groups").send({ - name: "TestGroup", - type: "party" - }).end(function(err, res) { - expectCode(res, 200); - group = res.body; - expect(group.members.length).to.equal(1); - expect(group.leader).to.equal(user._id); - return cb(); - }); - }, function(cb) { - return request.post(baseURL + '/user/tasks').send({ - type: 'daily', - text: 'daily one' - }).end(function(err, res) { - return cb(); - }); - }, function(cb) { - return request.post(baseURL + '/user/tasks').send({ - type: 'daily', - text: 'daily two' - }).end(function(err, res) { - return cb(); - }); - }, function(cb) { - return User.findByIdAndUpdate(user._id, { - $set: { - "stats.lvl": 50 - } - }, { - "new": true - }, function(err, _user) { - return cb(null, _user); - }); - }, function(_user, cb) { - var user; - user = _user; - return request.post(baseURL + "/user/batch-update").send([ - { - op: "score", - params: { - direction: "up", - id: user.dailys[0].id - } - }, { - op: "score", - params: { - direction: "up", - id: user.dailys[0].id - } - }, { - op: "update", - body: { - "stats.lvl": 50 - } - } - ]).end(function(err, res) { - user = res.body; - expect(user.party.quest.progress.up).to.be.above(0); - return async.waterfall([ - function(cb) { - return registerManyUsers(3, cb); - }, function(_party, cb) { - var inviteURL; - party = _party; - inviteURL = baseURL + "/groups/" + group._id + "/invite"; - return async.parallel([ - function(cb2) { - return request.post(inviteURL).send({ - uuids: [party[0]._id] - }).end(function() { - return cb2(); - }); - }, function(cb2) { - return request.post(inviteURL).send({ - uuids: [party[1]._id] - }).end(function() { - return cb2(); - }); - }, function(cb2) { - return request.post(inviteURL).send({ - uuids: [party[2]._id] - }).end(function(err, res) { - return cb2(); - }); - } - ], cb); - }, function(results, cb) { - var series; - series = _.reduce(party, function(m, v, i) { - m.push(function(cb2) { - return request.post(baseURL + "/groups/" + group._id + "/join").set("X-API-User", party[i]._id).set("X-API-Key", party[i].apiToken).end(function() { - return cb2(); - }); - }); - return m; - }, []); - return async.series(series, cb); - }, function(whatever, cb) { - return Group.findById(group._id, function(err, g) { - group = g; - expect(g.members.length).to.equal(4); - return cb(); - }); - } - ], function() { - return async.waterfall([ - function(cb) { - return request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end(function(err, res) { - expectCode(res, 400); - return User.findByIdAndUpdate(user._id, { - $set: { - "items.quests.vice3": 1 - } - }, { - "new": true - }, cb); - }); - }, function(_user, cb) { - return request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3").end(function(err, res) { - expectCode(res, 200); - return Group.findById(group._id, cb); - }); - }, function(_group, cb) { - expect(_group.quest.key).to.equal("vice3"); - expect(_group.quest.active).to.equal(false); - return request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[0]._id).set("X-API-Key", party[0].apiToken).end(function() { - return request.post(baseURL + "/groups/" + group._id + "/questAccept").set("X-API-User", party[1]._id).set("X-API-Key", party[1].apiToken).end(function(err, res) { - return request.post(baseURL + "/groups/" + group._id + "/questReject").set("X-API-User", party[2]._id).set("X-API-Key", party[2].apiToken).end(function(err, res) { - group = res.body; - expect(group.quest.active).to.equal(true); - return cb(); - }); - }); - }); - } - ], done); - }); - }); - } - ]); - }); - it("Casts a spell", function(done) { - var mp; - mp = user.stats.mp; - return request.get(baseURL + "/members/" + party[0]._id).end(function(err, res) { - party[0] = res.body; - return request.post(baseURL + "/user/class/cast/snowball?targetType=user&targetId=" + party[0]._id).end(function(err, res) { - return request.get(baseURL + "/members/" + party[0]._id).end(function(err, res) { - var difference, member; - member = res.body; - expect(member.achievements.snowball).to.equal(1); - expect(member.stats.buffs.snowball).to.exist; - difference = diff(member, party[0]); - expect(_.size(difference)).to.equal(2); - return request.put(baseURL + "/user").send({ - "stats.lvl": 5 - }).end(function(err, res) { - return request.put(baseURL + "/user").send({ - "stats.mp": 100 - }).end(function(err, res) { - return request.post(baseURL + "/user/class/cast/valorousPresence?targetType=party").end(function(err, res) { - return request.get(baseURL + "/members/" + member._id).end(function(err, res) { - expect(res.body.stats.buffs.str).to.be.above(0); - expect(diff(res.body, member).length).to.equal(1); - return done(); - }); - }); - }); - }); - }); - }); - }); - }); - it("Doesn't include people who aren't participating", function(done) { - return request.get(baseURL + "/groups/" + group._id).end(function(err, res) { - expect(_.size(res.body.quest.members)).to.equal(3); - return done(); - }); - }); - it("allows quest participants to leave quest", function(done) { - var leavingMember; - leavingMember = party[1]; - expect(group.quest.members[leavingMember._id]).to.eql(true); - return request.post(baseURL + "/groups/" + group._id + "/questLeave").set("X-API-User", leavingMember._id).set("X-API-Key", leavingMember.apiToken).end(function(err, res) { - expectCode(res, 204); - return request.get(baseURL + '/groups/party').end(function(err, res) { - expect(res.body.quest.members[leavingMember._id]).to.not.be.ok; - return done(); - }); - }); - }); - return xit("Hurts the boss", function(done) { - return request.post(baseURL + "/user/batch-update").end(function(err, res) { - var up, user; - user = res.body; - up = user.party.quest.progress.up; - expect(up).to.be.above(0); - return request.post(baseURL + "/user/batch-update").send([ - { - op: "score", - params: { - direction: "up", - id: user.dailys[0].id - } - }, { - op: "update", - body: { - lastCron: moment().subtract(1, "days") - } - } - ]).end(function(err, res) { - expect(res.body.party.quest.progress.up).to.be.above(up); - return request.post(baseURL + "/user/batch-update").end(function() { - return request.get(baseURL + "/groups/party").end(function(err, res) { - return async.waterfall([ - function(cb) { - return async.parallel([ - function(cb2) { - return Group.findById("habitrpg", { - quest: 1 - }, function(err, tavern) { - expect(tavern.quest.progress.hp).to.be.below(shared.content.quests.dilatory.boss.hp); - expect(tavern.quest.progress.rage).to.be.above(0); - return cb2(); - }); - }, function(cb2) { - var _party; - expect(res.body.quest.progress.hp).to.be.below(shared.content.quests.vice3.boss.hp); - _party = res.body.members; - expect(_.find(_party, { - _id: party[0]._id - }).stats.hp).to.be.below(50); - expect(_.find(_party, { - _id: party[1]._id - }).stats.hp).to.be.below(50); - expect(_.find(_party, { - _id: party[2]._id - }).stats.hp).to.be(50); - return cb2(); - } - ], cb); - }, function(whatever, cb) { - return async.waterfall([ - function(cb2) { - expect(user.items.pets["MantisShrimp-Base"]).to.not.be.ok(); - return Group.update({ - _id: "habitrpg" - }, { - $set: { - "quest.progress.hp": 0 - } - }, cb2); - }, function(arg1, arg2, cb2) { - expect(user.items.gear.owned.weapon_special_2).to.not.be.ok(); - return Group.findByIdAndUpdate(group._id, { - $set: { - "quest.progress.hp": 0 - } - }, { - "new": true - }, cb2); - } - ], cb); - }, function(_group, cb) { - return request.post(baseURL + "/user/batch-update").send([ - { - op: "score", - params: { - direction: "up", - id: user.dailys[1].id - } - }, { - op: "update", - body: { - lastCron: moment().subtract(1, "days") - } - } - ]).end(function() { - return cb(); - }); - }, function(cb) { - return request.post(baseURL + "/user/batch-update").end(function(err, res) { - return cb(null, res.body); - }); - }, function(_user, cb) { - return User.findById(_user._id, cb); - }, function(_user, cb) { - user = _user; - return Group.findById(group._id, cb); - }, function(_group, cb) { - var cummExp, cummGp; - cummExp = shared.content.quests.vice3.drop.exp + shared.content.quests.dilatory.drop.exp; - cummGp = shared.content.quests.vice3.drop.gp + shared.content.quests.dilatory.drop.gp; - return async.parallel([ - function(cb2) { - return Group.findById("habitrpg", function(err, tavern) { - expect(_.isEmpty(tavern.get("quest"))).to.equal(true); - expect(user.items.pets["MantisShrimp-Base"]).to.equal(5); - expect(user.items.mounts["MantisShrimp-Base"]).to.equal(true); - expect(user.items.eggs.Dragon).to.equal(2); - expect(user.items.hatchingPotions.Shade).to.equal(2); - return cb2(); - }); - }, function(cb2) { - expect(_.isEmpty(_group.get("quest"))).to.equal(true); - expect(user.items.gear.owned.weapon_special_2).to.equal(true); - expect(user.items.eggs.Dragon).to.equal(2); - expect(user.items.hatchingPotions.Shade).to.equal(2); - return async.parallel([ - function(cb3) { - return User.findById(party[0].id, function(err, mbr) { - expect(mbr.items.gear.owned.weapon_special_2).to.equal(true); - return cb3(); - }); - }, function(cb3) { - return User.findById(party[1].id, function(err, mbr) { - expect(mbr.items.gear.owned.weapon_special_2).to.equal(true); - return cb3(); - }); - }, function(cb3) { - return User.findById(party[2].id, function(err, mbr) { - expect(mbr.items.gear.owned.weapon_special_2).to.not.be.ok(); - return cb3(); - }); - } - ], cb2); - } - ], cb); - } - ], done); - }); - }); - }); - }); - }); - }); -}); diff --git a/test/api-legacy/pushNotifications.js b/test/api-legacy/pushNotifications.js deleted file mode 100644 index 7f98ddccfa..0000000000 --- a/test/api-legacy/pushNotifications.js +++ /dev/null @@ -1,430 +0,0 @@ -var app, rewire, sinon; - -app = require("../../website/server/server"); - -rewire = require('rewire'); - -sinon = require('sinon'); - -describe("Push-Notifications", function() { - before(function(done) { - return registerNewUser(done, true); - }); - return describe("Events that send push notifications", function() { - var pushSpy; - pushSpy = { - sendNotify: sinon.spy() - }; - afterEach(function(done) { - pushSpy.sendNotify.reset(); - return done(); - }); - context("Challenges", function() { - var challengeMock, challenges, userMock; - challenges = rewire("../../website/server/controllers/api-v2/challenges"); - challenges.__set__('pushNotify', pushSpy); - challengeMock = { - findById: function(arg, cb) { - return cb(null, { - leader: user._id, - name: 'challenge-name' - }); - } - }; - userMock = { - findById: function(arg, cb) { - return cb(null, user); - } - }; - challenges.__set__('Challenge', challengeMock); - challenges.__set__('User', userMock); - challenges.__set__('closeChal', function() { - return true; - }); - beforeEach(function(done) { - return registerNewUser(function() { - user.preferences.emailNotifications.wonChallenge = false; - user.save = function(cb) { - return cb(null, user); - }; - return done(); - }, true); - }); - return it("sends a push notification when you win a challenge", function(done) { - var req, res; - req = { - params: { - cid: 'challenge-id' - }, - query: { - uid: 'user-id' - } - }; - res = { - locals: { - user: user - } - }; - challenges.selectWinner(req, res); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledOnce; - expect(pushSpy.sendNotify).to.have.been.calledWith(user, 'You won a Challenge!', 'challenge-name'); - return done(); - }, 100); - }); - }); - context("Groups", function() { - var groups, recipient; - recipient = null; - groups = rewire("../../website/server/controllers/api-v2/groups"); - groups.__set__('pushNotify', pushSpy); - before(function(done) { - return registerNewUser(function(err, _user) { - var userMock; - recipient = _user; - recipient.invitations.guilds = []; - recipient.save = function(cb) { - return cb(null, recipient); - }; - recipient.preferences.emailNotifications.invitedGuild = false; - recipient.preferences.emailNotifications.invitedParty = false; - recipient.preferences.emailNotifications.invitedQuest = false; - userMock = { - findById: function(arg, cb) { - return cb(null, recipient); - }, - find: function(arg, arg2, cb) { - return cb(null, [recipient]); - }, - update: function(arg, arg2) { - return { - exec: function() { - return true; - } - }; - } - }; - groups.__set__('User', userMock); - return done(); - }, false); - }); - it("sends a push notification when invited to a guild", function(done) { - var group, req, res; - group = { - _id: 'guild-id', - name: 'guild-name', - type: 'guild', - members: [user._id], - invites: [] - }; - group.save = function(cb) { - return cb(null, group); - }; - req = { - body: { - uuids: [recipient._id] - } - }; - res = { - locals: { - group: group, - user: user - }, - json: function() { - return true; - } - }; - groups.invite(req, res); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledOnce; - expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Invited To Guild', group.name); - return done(); - }, 100); - }); - it("sends a push notification when invited to a party", function(done) { - var group, req, res; - group = { - _id: 'party-id', - name: 'party-name', - type: 'party', - members: [user._id], - invites: [] - }; - group.save = function(cb) { - return cb(null, group); - }; - req = { - body: { - uuids: [recipient._id] - } - }; - res = { - locals: { - group: group, - user: user - }, - json: function() { - return true; - } - }; - groups.invite(req, res); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledOnce; - expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Invited To Party', group.name); - return done(); - }, 100); - }); - it("sends a push notification when invited to a quest", function(done) { - var group, req, res; - group = { - _id: 'party-id', - name: 'party-name', - type: 'party', - members: [user._id, recipient._id], - invites: [], - quest: {} - }; - user.items.quests.hedgehog = 5; - group.save = function(cb) { - return cb(null, group); - }; - group.markModified = function() { - return true; - }; - req = { - body: { - uuids: [recipient._id] - }, - query: { - key: 'hedgehog' - } - }; - res = { - locals: { - group: group, - user: user - }, - json: function() { - return true; - } - }; - groups.questAccept(req, res); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledOnce; - expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Quest Invitation', 'Invitation for the Quest The Hedgebeast'); - return done(); - }, 100); - }); - return it("sends a push notification to participating members when quest starts", function(done) { - var group, req, res, userMock; - group = { - _id: 'party-id', - name: 'party-name', - type: 'party', - members: [user._id, recipient._id], - invites: [] - }; - group.quest = { - key: 'hedgehog', - progress: { - hp: 100 - }, - members: {} - }; - group.quest.members[recipient._id] = true; - group.save = function(cb) { - return cb(null, group); - }; - group.markModified = function() { - return true; - }; - req = { - body: { - uuids: [recipient._id] - }, - query: {} - }; - res = { - locals: { - group: group, - user: user - }, - json: function() { - return true; - } - }; - userMock = { - findOne: function(arg, arg2, cb) { - return cb(null, recipient); - }, - update: function(arg, arg2, cb) { - if (cb) { - return cb(null, user); - } else { - return { - exec: function() { - return true; - } - }; - } - } - }; - groups.__set__('User', userMock); - groups.__set__('populateQuery', function(arg, arg2, arg3) { - return { - exec: function() { - return group.members; - } - }; - }); - groups.questAccept(req, res); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledTwice; - expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'HabitRPG', 'Your Quest has Begun: The Hedgebeast'); - return done(); - }, 100); - }); - }); - return describe("Gifts", function() { - var recipient; - recipient = null; - before(function(done) { - return registerNewUser(function(err, _user) { - recipient = _user; - recipient.preferences.emailNotifications.giftedGems = false; - user.balance = 4; - user.save = function() { - return true; - }; - recipient.save = function() { - return true; - }; - return done(); - }, false); - }); - context("sending gems from balance", function() { - var members; - members = rewire("../../website/server/controllers/api-v2/members"); - members.sendMessage = function() { - return true; - }; - members.__set__('pushNotify', pushSpy); - members.__set__('fetchMember', function(id) { - return function(cb) { - return cb(null, recipient); - }; - }); - return it("sends a push notification", function(done) { - var req, res; - req = { - params: { - uuid: "uuid" - }, - body: { - type: 'gems', - gems: { - amount: 1 - } - } - }; - res = { - locals: { - user: user - } - }; - members.sendGift(req, res); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledOnce; - expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Gems', '1 Gems - by ' + user.profile.name); - return done(); - }, 100); - }); - }); - return describe("Purchases", function() { - var membersMock, payments; - payments = rewire("../../website/server/controllers/payments"); - payments.__set__('pushNotify', pushSpy); - membersMock = { - sendMessage: function() { - return true; - } - }; - payments.__set__('members', membersMock); - context("buying gems as a purchased gift", function() { - it("sends a push notification", function(done) { - var data; - data = { - user: user, - gift: { - member: recipient, - gems: { - amount: 1 - } - } - }; - payments.buyGems(data); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledOnce; - expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Gems', '1 Gems - by ' + user.profile.name); - return done(); - }, 100); - }); - return it("does not send a push notification if buying gems for self", function(done) { - var data; - data = { - user: user, - gift: { - member: user, - gems: { - amount: 1 - } - } - }; - payments.buyGems(data); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.not.have.been.called; - return done(); - }, 100); - }); - }); - return context("sending a subscription as a purchased gift", function() { - it("sends a push notification", function(done) { - var data; - data = { - user: user, - gift: { - member: recipient, - subscription: { - key: 'basic_6mo' - } - } - }; - payments.createSubscription(data); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.have.been.calledOnce; - expect(pushSpy.sendNotify).to.have.been.calledWith(recipient, 'Gifted Subscription', '6 months - by ' + user.profile.name); - return done(); - }, 100); - }); - return it("does not send a push notification if buying subscription for self", function(done) { - var data; - data = { - user: user, - gift: { - member: user, - subscription: { - key: 'basic_6mo' - } - } - }; - payments.createSubscription(data); - return setTimeout(function() { - expect(pushSpy.sendNotify).to.not.have.been.called; - return done(); - }, 100); - }); - }); - }); - }); - }); -}); diff --git a/test/api-legacy/score.js b/test/api-legacy/score.js deleted file mode 100644 index af31a4f334..0000000000 --- a/test/api-legacy/score.js +++ /dev/null @@ -1,142 +0,0 @@ -require("../../website/server/server"); - -describe("Score", function() { - before(function(done) { - return registerNewUser(done, true); - }); - context("Todos that did not previously exist", function() { - it("creates a completed a todo when using up url", function(done) { - return request.post(baseURL + "/user/tasks/withUp/up").send({ - type: "todo", - text: "withUp" - }).end(function(err, res) { - expectCode(res, 200); - request.get(baseURL + "/user/tasks/withUp").end(function(err, res) { - var upTodo; - upTodo = res.body; - return expect(upTodo.completed).to.equal(true); - }); - return done(); - }); - }); - it("creates an uncompleted todo when using down url", function(done) { - return request.post(baseURL + "/user/tasks/withDown/down").send({ - type: "todo", - text: "withDown" - }).end(function(err, res) { - expectCode(res, 200); - return request.get(baseURL + "/user/tasks/withDown").end(function(err, res) { - var downTodo; - downTodo = res.body; - expect(downTodo.completed).to.equal(false); - return done(); - }); - }); - }); - it("creates a completed a todo overriding the complete parameter when using up url", function(done) { - return request.post(baseURL + "/user/tasks/withUpWithComplete/up").send({ - type: "todo", - text: "withUpWithComplete", - completed: false - }).end(function(err, res) { - expectCode(res, 200); - return request.get(baseURL + "/user/tasks/withUpWithComplete").end(function(err, res) { - var upTodo; - upTodo = res.body; - expect(upTodo.completed).to.equal(true); - return done(); - }); - }); - }); - return it("Creates an uncompleted todo overriding the completed parameter when using down url", function(done) { - return request.post(baseURL + "/user/tasks/withDownWithUncomplete/down").send({ - type: "todo", - text: "withDownWithUncomplete", - completed: true - }).end(function(err, res) { - expectCode(res, 200); - return request.get(baseURL + "/user/tasks/withDownWithUncomplete").end(function(err, res) { - var downTodo; - downTodo = res.body; - expect(downTodo.completed).to.equal(false); - return done(); - }); - }); - }); - }); - context("Todo that already exists", function() { - it("It completes a todo when using up url", function(done) { - return request.post(baseURL + "/user/tasks").send({ - type: "todo", - text: "Sample Todo" - }).end(function(err, res) { - var unCompletedTodo; - expectCode(res, 200); - unCompletedTodo = res.body; - expect(unCompletedTodo.completed).to.equal(false); - return request.post(baseURL + "/user/tasks/" + unCompletedTodo._id + "/up").end(function(err, res) { - expectCode(res, 200); - return request.get(baseURL + "/user/tasks/" + unCompletedTodo._id).end(function(err, res) { - unCompletedTodo = res.body; - expect(unCompletedTodo.completed).to.equal(true); - return done(); - }); - }); - }); - }); - return it("It uncompletes a todo when using down url", function(done) { - return request.post(baseURL + "/user/tasks").send({ - type: "todo", - text: "Sample Todo", - completed: true - }).end(function(err, res) { - var completedTodo; - expectCode(res, 200); - completedTodo = res.body; - expect(completedTodo.completed).to.equal(true); - return request.post(baseURL + "/user/tasks/" + completedTodo._id + "/down").end(function(err, res) { - expectCode(res, 200); - return request.get(baseURL + "/user/tasks/" + completedTodo._id).end(function(err, res) { - completedTodo = res.body; - expect(completedTodo.completed).to.equal(false); - return done(); - }); - }); - }); - }); - }); - it("Creates and scores up a habit when using up url", function(done) { - var upHabit; - upHabit = void 0; - return request.post(baseURL + "/user/tasks/habitWithUp/up").send({ - type: "habit", - text: "testTitle", - completed: true - }).end(function(err, res) { - expectCode(res, 200); - return request.get(baseURL + "/user/tasks/habitWithUp").end(function(err, res) { - upHabit = res.body; - expect(upHabit.value).to.be.at.least(1); - expect(upHabit.type).to.equal("habit"); - return done(); - }); - }); - }); - return it("Creates and scores down a habit when using down url", function(done) { - var downHabit; - downHabit = void 0; - return request.post(baseURL + "/user/tasks/habitWithDown/down").send({ - type: "habit", - text: "testTitle", - completed: true - }).end(function(err, res) { - expectCode(res, 200); - return request.get(baseURL + "/user/tasks/habitWithDown").end(function(err, res) { - downHabit = res.body; - expect(downHabit.value).to.have.at.most(-1); - expect(downHabit.type).to.equal("habit"); - return done(); - }); - }); - }); -}); diff --git a/test/api-legacy/subscriptions.js b/test/api-legacy/subscriptions.js deleted file mode 100644 index 73b55039df..0000000000 --- a/test/api-legacy/subscriptions.js +++ /dev/null @@ -1,50 +0,0 @@ -var app, payments; - -payments = require("../../website/server/controllers/payments"); - -app = require("../../website/server/server"); - -describe("Subscriptions", function() { - before(function(done) { - return registerNewUser(done, true); - }); - return it("Handles unsubscription", function(done) { - var cron; - cron = function() { - user.lastCron = moment().subtract(1, "d"); - return user.fns.cron(); - }; - expect(user.purchased.plan.customerId).to.not.exist; - payments.createSubscription({ - user: user, - customerId: "123", - paymentMethod: "Stripe", - sub: { - key: 'basic_6mo' - } - }); - expect(user.purchased.plan.customerId).to.exist; - shared.wrap(user); - cron(); - expect(user.purchased.plan.customerId).to.exist; - payments.cancelSubscription({ - user: user - }); - cron(); - expect(user.purchased.plan.customerId).to.exist; - expect(user.purchased.plan.dateTerminated).to.exist; - user.purchased.plan.dateTerminated = moment().subtract(2, "d"); - cron(); - expect(user.purchased.plan.customerId).to.not.exist; - payments.createSubscription({ - user: user, - customerId: "123", - paymentMethod: "Stripe", - sub: { - key: 'basic_6mo' - } - }); - expect(user.purchased.plan.dateTerminated).to.not.exist; - return done(); - }); -}); diff --git a/test/api-legacy/todos.js b/test/api-legacy/todos.js deleted file mode 100644 index b72ea57223..0000000000 --- a/test/api-legacy/todos.js +++ /dev/null @@ -1,87 +0,0 @@ -require("../../website/server/server"); - -describe("Todos", function() { - before(function(done) { - return registerNewUser(done, true); - }); - beforeEach(function(done) { - return User.findById(user._id, function(err, _user) { - var user; - user = _user; - shared.wrap(user); - return done(); - }); - }); - return it("Archives old todos", function(done) { - var numTasks; - numTasks = _.size(user.todos); - return request.post(baseURL + "/user/batch-update?_v=999").send([ - { - op: "addTask", - body: { - type: "todo" - } - }, { - op: "addTask", - body: { - type: "todo" - } - }, { - op: "addTask", - body: { - type: "todo" - } - } - ]).end(function(err, res) { - expectCode(res, 200); - expect(_.size(res.body.todos)).to.equal(numTasks + 3); - numTasks += 3; - return request.post(baseURL + "/user/batch-update?_v=998").send([ - { - op: "score", - params: { - direction: "up", - id: res.body.todos[0].id - } - }, { - op: "score", - params: { - direction: "up", - id: res.body.todos[1].id - } - }, { - op: "score", - params: { - direction: "up", - id: res.body.todos[2].id - } - } - ]).end(function(err, res) { - expectCode(res, 200); - expect(_.size(res.body.todos)).to.equal(numTasks); - return request.post(baseURL + "/user/batch-update?_v=997").send([ - { - op: "updateTask", - params: { - id: res.body.todos[0].id - }, - body: { - dateCompleted: moment().subtract(4, "days") - } - }, { - op: "updateTask", - params: { - id: res.body.todos[1].id - }, - body: { - dateCompleted: moment().subtract(4, "days") - } - } - ]).end(function(err, res) { - expect(_.size(res.body.todos)).to.equal(numTasks - 2); - return done(); - }); - }); - }); - }); -}); diff --git a/test/api/v2/challenges/GET-challenges_id.test.js b/test/api/v2/challenges/GET-challenges_id.test.js deleted file mode 100644 index dab8a71c0a..0000000000 --- a/test/api/v2/challenges/GET-challenges_id.test.js +++ /dev/null @@ -1,34 +0,0 @@ -import { - createAndPopulateGroup, - generateChallenge, -} from '../../../helpers/api-integration/v2'; - -describe('GET /challenges/:id', () => { - context('Member of a challenge', () => { - let leader, party, challenge; - - before(async () => { - let { - group, - groupLeader, - } = await createAndPopulateGroup(); - - party = group; - leader = groupLeader; - challenge = await generateChallenge(leader, party, { - name: 'a created challenge', - shortName: 'aCreatedChallenge', - description: 'a description for the challenge', - }); - }); - - it('returns the challenge object', async () => { - let fetchedChallenge = await leader.get(`/challenges/${challenge._id}`); - - expect(fetchedChallenge.name).to.eql(challenge.name); - expect(fetchedChallenge.shortName).to.eql(challenge.shortName); - expect(fetchedChallenge.description).to.eql(challenge.description); - expect(fetchedChallenge.members).to.have.a.lengthOf(1); - }); - }); -}); diff --git a/test/api/v2/groups/GET-groups.test.js b/test/api/v2/groups/GET-groups.test.js deleted file mode 100644 index d941b2533e..0000000000 --- a/test/api/v2/groups/GET-groups.test.js +++ /dev/null @@ -1,100 +0,0 @@ -import { - generateGroup, - generateUser, - resetHabiticaDB, -} from '../../../helpers/api-integration/v2'; -import { - TAVERN_ID, -} from '../../../../website/server/models/group'; - -describe('GET /groups', () => { - const NUMBER_OF_PUBLIC_GUILDS = 3; - - let user; - let leader; - - before(async () => { - // Set up a world with a mixture of public and private guilds - // Invite user to a few of them - await resetHabiticaDB(); - - user = await generateUser(); - leader = await generateUser({ balance: 10 }); - - await generateGroup(leader, { - name: 'public guild - is member', - type: 'guild', - privacy: 'public', - }, { - members: [leader._id, user._id], - }); - - await generateGroup(leader, { - name: 'public guild - is not member', - type: 'guild', - privacy: 'public', - }); - - await generateGroup(leader, { - name: 'private guild - is member', - type: 'guild', - privacy: 'private', - }, { - members: [leader._id, user._id], - }); - - await generateGroup(leader, { - name: 'private guild - is not member', - type: 'guild', - privacy: 'private', - }); - - await generateGroup(leader, { - name: 'party - is not member', - type: 'party', - privacy: 'private', - }); - - await user.post('/groups', { - name: 'party - is member', - type: 'party', - privacy: 'private', - }); - }); - - context('no query passed in', () => { - xit('lists all public guilds, the tavern, user\'s party, and any private guilds that user is a part of - TODO query includes duplicates - IE, tavern is included as tavern and part of public guilds. Refactor so this is not the case'); - }); - - context('tavern passed in as query', () => { - it('returns only the tavern', async () => { - await expect(user.get('/groups', null, {type: 'tavern'})) - .to.eventually.have.a.lengthOf(1) - .and.to.have.deep.property('[0]') - .and.to.have.property('_id', TAVERN_ID); - }); - }); - - context('party passed in as query', () => { - it('returns only the user\'s party', async () => { - await expect(user.get('/groups', null, {type: 'party'})) - .to.eventually.have.a.lengthOf(1) - .and.to.have.deep.property('[0]') - .and.to.have.property('leader', user._id); - }); - }); - - context('public passed in as query', () => { - it('returns all public guilds', async () => { - await expect(user.get('/groups', null, {type: 'public'})) - .to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS); - }); - }); - - context('guilds passed in as query', () => { - it('returns all guilds user is a part of ', async () => { - await expect(leader.get('/groups', null, {type: 'guilds'})) - .to.eventually.have.a.lengthOf(4); - }); - }); -}); diff --git a/test/api/v2/groups/GET-groups_id.test.js b/test/api/v2/groups/GET-groups_id.test.js deleted file mode 100644 index 1de69da586..0000000000 --- a/test/api/v2/groups/GET-groups_id.test.js +++ /dev/null @@ -1,322 +0,0 @@ -import { - createAndPopulateGroup, - generateUser, - translate as t, -} from '../../../helpers/api-integration/v2'; -import { - find, - each, -} from 'lodash'; - -describe('GET /groups/:id', () => { - let typesOfGroups = {}; - typesOfGroups['public guild'] = { type: 'guild', privacy: 'public' }; - typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' }; - typesOfGroups.party = { type: 'party', privacy: 'private' }; - - each(typesOfGroups, (groupDetails, groupType) => { - context(`Member of a ${groupType}`, () => { - let leader, member, createdGroup; - - before(async () => { - let groupData = await createAndPopulateGroup({ - members: 30, - groupDetails, - }); - - leader = groupData.groupLeader; - member = groupData.members[0]; - createdGroup = groupData.group; - }); - - it('returns the group object', async () => { - let group = await member.get(`/groups/${createdGroup._id}`); - - expect(group._id).to.eql(createdGroup._id); - expect(group.name).to.eql(createdGroup.name); - expect(group.type).to.eql(createdGroup.type); - expect(group.privacy).to.eql(createdGroup.privacy); - }); - - it('transforms members array to an array of user objects', async () => { - let group = await member.get(`/groups/${createdGroup._id}`); - let someMember = group.members[0]; - - expect(someMember._id).to.exist; - expect(someMember.profile.name).to.exist; - expect(someMember.contributor).to.exist; - expect(someMember.achievements).to.exist; - expect(someMember.items).to.exist; - }); - - it('transforms leader id to leader object', async () => { - let group = await member.get(`/groups/${createdGroup._id}`); - - expect(group.leader._id).to.eql(leader._id); - expect(group.leader.profile.name).to.eql(leader.profile.name); - expect(group.leader.items).to.exist; - expect(group.leader.stats).to.exist; - expect(group.leader.achievements).to.exist; - expect(group.leader.contributor).to.exist; - }); - - it('includes the user in the members list', async () => { - let group = await member.get(`/groups/${createdGroup._id}`); - let userInGroup = find(group.members, '_id', member._id); - - expect(userInGroup).to.exist; - }); - }); - }); - - context('flagged messages', () => { - let group; - - let chat1 = { - id: 'chat1', - text: 'chat 1', - flags: {}, - }; - - let chat2 = { - id: 'chat2', - text: 'chat 2', - flags: {}, - flagCount: 0, - }; - - let chat3 = { - id: 'chat3', - text: 'chat 3', - flags: { - 'user-id': true, - }, - flagCount: 1, - }; - - let chat4 = { - id: 'chat4', - text: 'chat 4', - flags: { - 'user-id': true, - 'other-user-id': true, - }, - flagCount: 2, - }; - - let chat5 = { - id: 'chat5', - text: 'chat 5', - flags: { - 'user-id': true, - 'other-user-id': true, - 'yet-another-user-id': true, - }, - flagCount: 3, - }; - - beforeEach(async () => { - let groupData = await createAndPopulateGroup({ - groupDetails: { - name: 'test guild', - type: 'guild', - privacy: 'public', - chat: [ - chat1, - chat2, - chat3, - chat4, - chat5, - ], - }, - }); - - group = groupData.group; - }); - - context('non-admin', () => { - let nonAdmin; - - beforeEach(async () => { - nonAdmin = await generateUser(); - }); - - it('does not include messages with a flag count of 2 or greater', async () => { - let fetchedGroup = await nonAdmin.get(`/groups/${group._id}`); - - expect(fetchedGroup.chat).to.have.lengthOf(3); - expect(fetchedGroup.chat[0].id).to.eql(chat1.id); - expect(fetchedGroup.chat[1].id).to.eql(chat2.id); - expect(fetchedGroup.chat[2].id).to.eql(chat3.id); - }); - - it('does not include user ids in flags object', async () => { - let fetchedGroup = await nonAdmin.get(`/groups/${group._id}`); - let chatWithOneFlag = fetchedGroup.chat[2]; - - expect(chatWithOneFlag.id).to.eql(chat3.id); - expect(chat3.flags).to.eql({ 'user-id': true }); - expect(chatWithOneFlag.flags).to.eql({}); - }); - }); - - context('admin', () => { - let admin; - - beforeEach(async () => { - admin = await generateUser({ - 'contributor.admin': true, - }); - }); - - it('includes all messages', async () => { - let fetchedGroup = await admin.get(`/groups/${group._id}`); - - expect(fetchedGroup.chat).to.have.lengthOf(5); - expect(fetchedGroup.chat[0].id).to.eql(chat1.id); - expect(fetchedGroup.chat[1].id).to.eql(chat2.id); - expect(fetchedGroup.chat[2].id).to.eql(chat3.id); - expect(fetchedGroup.chat[3].id).to.eql(chat4.id); - expect(fetchedGroup.chat[4].id).to.eql(chat5.id); - }); - - it('includes user ids in flags object', async () => { - let fetchedGroup = await admin.get(`/groups/${group._id}`); - let chatWithOneFlag = fetchedGroup.chat[2]; - - expect(chatWithOneFlag.id).to.eql(chat3.id); - expect(chat3.flags).to.eql({ 'user-id': true }); - expect(chatWithOneFlag.flags).to.eql(chat3.flags); - }); - }); - }); - - context('Non-member of a public guild', () => { - let nonMember, createdGroup; - - before(async () => { - let groupData = await createAndPopulateGroup({ - members: 1, - groupDetails: { - name: 'test guild', - type: 'guild', - privacy: 'public', - }, - }); - - createdGroup = groupData.group; - nonMember = await generateUser(); - }); - - it('returns the group object for a non-member', async () => { - let group = await nonMember.get(`/groups/${createdGroup._id}`); - - expect(group._id).to.eql(createdGroup._id); - expect(group.name).to.eql(createdGroup.name); - expect(group.type).to.eql(createdGroup.type); - expect(group.privacy).to.eql(createdGroup.privacy); - }); - - it('does not include user in members list', async () => { - let group = await nonMember.get(`/groups/${createdGroup._id}`); - let userInGroup = find(group.members, '_id', nonMember._id); - - expect(userInGroup).to.not.exist; - }); - }); - - context('Private Guilds', () => { - let nonMember, createdGroup; - - before(async () => { - let groupData = await createAndPopulateGroup({ - members: 1, - groupDetails: { - name: 'test guild', - type: 'guild', - privacy: 'private', - }, - }); - - createdGroup = groupData.group; - nonMember = await generateUser(); - }); - - it('does not return the group object for a non-member', async () => { - await expect(nonMember.get(`/groups/${createdGroup._id}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - text: t('messageGroupNotFound'), - }); - }); - }); - - context('Non-member of a party', () => { - let nonMember, createdGroup; - - before(async () => { - let groupData = await createAndPopulateGroup({ - members: 1, - groupDetails: { - name: 'test party', - type: 'party', - privacy: 'private', - }, - }); - - createdGroup = groupData.group; - nonMember = await generateUser(); - }); - - it('does not return the group object for a non-member', async () => { - await expect(nonMember.get(`/groups/${createdGroup._id}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - text: t('messageGroupNotFound'), - }); - }); - }); - - context('Member of a party', () => { - let member, createdGroup; - - before(async () => { - let groupData = await createAndPopulateGroup({ - members: 1, - groupDetails: { - name: 'test party', - type: 'party', - privacy: 'private', - }, - }); - - createdGroup = groupData.group; - member = groupData.members[0]; - }); - - it('returns the user\'s party if an id of "party" is passed in', async () => { - let group = await member.get('/groups/party'); - - expect(group._id).to.eql(createdGroup._id); - expect(group.name).to.eql(createdGroup.name); - expect(group.type).to.eql(createdGroup.type); - expect(group.privacy).to.eql(createdGroup.privacy); - }); - }); - - context('Non-existent group', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('returns error if group does not exist', async () => { - await expect(user.get('/groups/group-that-does-not-exist')) - .to.eventually.be.rejected.and.eql({ - code: 404, - text: t('messageGroupNotFound'), - }); - }); - }); -}); diff --git a/test/api/v2/groups/POST-groups.test.js b/test/api/v2/groups/POST-groups.test.js deleted file mode 100644 index 8da165aaf7..0000000000 --- a/test/api/v2/groups/POST-groups.test.js +++ /dev/null @@ -1,144 +0,0 @@ -import { - generateGroup, - generateUser, - translate as t, -} from '../../../helpers/api-integration/v2'; - -describe('POST /groups', () => { - context('All groups', () => { - let leader; - - beforeEach(async () => { - leader = await generateUser(); - }); - - xit('returns defaults? (TODO: it\'s possible to create a group without a type. Should the group default to party? Should we require type to be set?', async () => { - return leader.post('/groups').then((group) => { - expect(group._id).to.exist; - expect(group.name).to.eql(`${leader.profile.name}'s group`); - expect(group.type).to.eql('party'); - expect(group.privacy).to.eql('private'); - }); - }); - - it('returns a group object', async () => { - let group = await leader.post('/groups', { - name: 'Test Group', - type: 'party', - leaderOnly: { challenges: true }, - description: 'Test Group Description', - leaderMessage: 'Test Group Message', - }); - - expect(group._id).to.exist; - expect(group.leader).to.eql(leader._id); - expect(group.name).to.eql(group.name); - expect(group.description).to.eql(group.description); - expect(group.leaderMessage).to.eql(group.leaderMessage); - expect(group.leaderOnly).to.eql(group.leaderOnly); - expect(group.memberCount).to.eql(1); - }); - - it('returns a populated members array', async () => { - let party = await leader.post('/groups', { - type: 'party', - }); - - let member = party.members[0]; - - expect(member._id).to.eql(leader._id); - expect(member.profile).to.eql(leader.profile); - expect(member.contributor).to.eql(leader.contributor); - }); - }); - - context('Parties', () => { - let leader; - - beforeEach(async () => { - leader = await generateUser(); - }); - - it('allows party creation without gems', async () => { - let party = await leader.post('/groups', { - type: 'party', - }); - - expect(party._id).to.exist; - }); - - it('prevents party creation if user is already in party', async () => { - await generateGroup(leader, { - name: 'first party that user attempts to create', - type: 'party', - }); - - await expect(leader.post('/groups', { type: 'party' })).to.eventually.be.rejected.and.eql({ - code: 400, - text: t('messageGroupAlreadyInParty'), - }); - }); - - xit('prevents creating a public party. TODO: it is possible to create a public party. Should we send back an error? Automatically switch the privacy to private?', async () => { - return expect(leader.post('/groups', { - type: 'party', - privacy: 'public', - })).to.eventually.be.rejected.and.eql({ - code: 400, - text: 'Parties must be private', - }); - }); - }); - - context('Guilds', () => { - let leader; - - beforeEach(async () => { - leader = await generateUser({ - balance: 2, - }); - }); - - it('prevents guild creation when user does not have enough gems', async () => { - let userWithoutGems = await generateUser({ - balance: 0.75, - }); - - await expect(userWithoutGems.post('/groups', { type: 'guild' })).to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageInsufficientGems'), - }); - }); - - it('can create a public guild', async () => { - let guild = await leader.post('/groups', { - type: 'guild', - privacy: 'public', - }); - - expect(guild.leader).to.eql(leader._id); - }); - - it('can create a private guild', async () => { - let privateGuild = await leader.post('/groups', { - type: 'guild', - privacy: 'private', - }); - - expect(privateGuild.leader).to.eql(leader._id); - }); - - it('deducts gems from user and adds them to guild bank', async () => { - let guild = await leader.post('/groups', { - type: 'guild', - privacy: 'private', - }); - - expect(guild.balance).to.eql(1); - - let updatedUser = await leader.get('/user'); - - expect(updatedUser.balance).to.eql(1); - }); - }); -}); diff --git a/test/api/v2/groups/POST-groups_id.test.js b/test/api/v2/groups/POST-groups_id.test.js deleted file mode 100644 index 9799ecff4e..0000000000 --- a/test/api/v2/groups/POST-groups_id.test.js +++ /dev/null @@ -1,67 +0,0 @@ -import { - generateGroup, - generateUser, - translate as t, -} from '../../../helpers/api-integration/v2'; - -describe('POST /groups/:id', () => { - context('user is not the leader of the group', () => { - let user, otherUser, groupUserDoesNotOwn; - - beforeEach(async () => { - return Promise.all([ - generateUser({ balance: 10 }), - generateUser({ balance: 10 }), - ]).then((users) => { - user = users[0]; - otherUser = users[1]; - - return generateGroup(otherUser, { - name: 'Group not Owned By User', - type: 'guild', - privacy: 'public', - members: [user, otherUser], - }); - }).then((group) => { - groupUserDoesNotOwn = group; - }); - }); - - it('does not allow user to update group', async () => { - return expect(user.post(`/groups/${groupUserDoesNotOwn._id}`, { - name: 'Change', - })).to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageGroupOnlyLeaderCanUpdate'), - }); - }); - }); - - context('user is the leader of the group', () => { - let user, usersGroup; - - beforeEach(async () => { - user = await generateUser({ - balance: 10, - }); - - usersGroup = await generateGroup(user, { - name: 'Original Group Title', - type: 'guild', - privacy: 'public', - }); - }); - - it('allows user to update group', async () => { - await user.post(`/groups/${usersGroup._id}`, { - name: 'New Group Title', - description: 'New group description', - }); - - await usersGroup.sync(); - - expect(usersGroup.name).to.eql('New Group Title'); - expect(usersGroup.description).to.eql('New group description'); - }); - }); -}); diff --git a/test/api/v2/groups/POST-groups_id_invite.test.js b/test/api/v2/groups/POST-groups_id_invite.test.js deleted file mode 100644 index ef768dbf7e..0000000000 --- a/test/api/v2/groups/POST-groups_id_invite.test.js +++ /dev/null @@ -1,89 +0,0 @@ -import { - createAndPopulateGroup, - generateUser, -} from '../../../helpers/api-integration/v2'; -import { each } from 'lodash'; - -describe('POST /groups/:id/invite', () => { - context('user is a member of the group', () => { - each({ - 'public guild': {type: 'guild', privacy: 'public'}, - 'private guild': {type: 'guild', privacy: 'private'}, - party: {type: 'party', privacy: 'private'}, - }, (groupDetails, groupType) => { - let group, invitee, inviter; - - beforeEach(async () => { - invitee = await generateUser(); - let groupData = await createAndPopulateGroup({ - groupDetails, - members: 1, - }); - group = groupData.group; - inviter = groupData.members[0]; - }); - - it(`allows user to send an invitation for a ${groupType}`, async () => { - await inviter.post(`/groups/${group._id}/invite`, { - uuids: [invitee._id], - }); - group = await inviter.get(`/groups/${group._id}`); - expect(_.find(group.invites, {_id: invitee._id})._id).to.exists; - }); - }); - }); - - context('user is a not member of the group', () => { - each({ - 'public guild': {type: 'guild', privacy: 'public'}, - }, (groupDetails, groupType) => { - context(`the group is a ${groupType}`, () => { - let group, invitee, inviter; - - beforeEach(async () => { - invitee = await generateUser(); - inviter = await generateUser(); - let groupData = await createAndPopulateGroup({ - groupDetails, - }); - group = groupData.group; - }); - - it(`allows user to send an invitation for a ${groupType}`, async () => { - await inviter.post(`/groups/${group._id}/invite`, { - uuids: [invitee._id], - }); - group = await inviter.get(`/groups/${group._id}`); - expect(_.find(group.invites, {_id: invitee._id})._id).to.exists; - }); - }); - }); - - each({ - 'private guild': {type: 'guild', privacy: 'private'}, - party: {type: 'party', privacy: 'private'}, - }, (groupDetails, groupType) => { - context(`the group is a ${groupType}`, () => { - let group, invitee, inviter; - - beforeEach(async () => { - invitee = await generateUser(); - inviter = await generateUser(); - let groupData = await createAndPopulateGroup({ - groupDetails, - }); - group = groupData.group; - }); - - it(`does not allows user to send an invitation for a ${groupType}`, async () => { - return expect(inviter.post(`/groups/${group._id}/invite`, { - uuids: [invitee._id], - })).to.eventually.be.rejected.and.eql({ - code: 401, - text: 'Only a member can invite new members!', - }); - }); - }); - }); - }); -}); diff --git a/test/api/v2/groups/POST-groups_id_join.test.js b/test/api/v2/groups/POST-groups_id_join.test.js deleted file mode 100644 index cf216354a0..0000000000 --- a/test/api/v2/groups/POST-groups_id_join.test.js +++ /dev/null @@ -1,110 +0,0 @@ -import { - createAndPopulateGroup, - generateUser, - translate as t, -} from '../../../helpers/api-integration/v2'; -import { each } from 'lodash'; - -describe('POST /groups/:id/join', () => { - context('user is already a member of the group', () => { - it('returns an error'); - }); - - each({ - 'public guild': {type: 'guild', privacy: 'public'}, - 'private guild': {type: 'guild', privacy: 'private'}, - party: {type: 'party', privacy: 'private'}, - }, (groupDetails, groupType) => { - context(`user has invitation to a ${groupType}`, () => { - let group, invitee; - - beforeEach(async () => { - let groupData = await createAndPopulateGroup({ - groupDetails, - invites: 1, - }); - group = groupData.group; - invitee = groupData.invitees[0]; - }); - - it(`allows user to join a ${groupType}`, async () => { - await invitee.post(`/groups/${group._id}/join`); - - group = await invitee.get(`/groups/${group._id}`); - expect(_.find(group.members, {_id: invitee._id})._id).to.exists; - }); - }); - }); - - each({ - 'private guild': {type: 'guild', privacy: 'private'}, - party: {type: 'party', privacy: 'private'}, - }, (groupDetails, groupType) => { - context(`user does not have an invitation to a ${groupType}`, () => { - let group, user; - - beforeEach(async () => { - let groupData = await createAndPopulateGroup({ - groupDetails, - }); - group = groupData.group; - user = await generateUser(); - }); - - it(`does not allow user to join a ${groupType}`, async () => { - await expect(user.post(`/groups/${group._id}/join`)).to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageGroupRequiresInvite'), - }); - }); - }); - }); - - context('user does not have an invitation to a public group', () => { - let group, user; - - beforeEach(async () => { - let groupData = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - }); - group = groupData.group; - user = await generateUser(); - }); - - it('allows user to join a public guild', async () => { - await user.post(`/groups/${group._id}/join`); - - group = await user.get(`/groups/${group._id}`); - - expect(_.find(group.members, {_id: user._id})._id).to.exists; - }); - }); - - context('public guild has no leader', () => { - let user, group; - - beforeEach(async () => { - let groupData = await createAndPopulateGroup({ - groupDetails: { - name: 'test guild', - type: 'guild', - privacy: 'public', - }, - }); - group = groupData.group; - await groupData.groupLeader.post(`/groups/${group._id}/leave`); - user = await generateUser(); - }); - - it('makes the joining user the leader', async () => { - await user.post(`/groups/${group._id}/join`); - - group = await user.get(`/groups/${group._id}`); - - expect(group.leader._id).to.eql(user._id); - }); - }); -}); diff --git a/test/api/v2/groups/POST-groups_id_leave.test.js b/test/api/v2/groups/POST-groups_id_leave.test.js deleted file mode 100644 index f6511e941c..0000000000 --- a/test/api/v2/groups/POST-groups_id_leave.test.js +++ /dev/null @@ -1,136 +0,0 @@ -import { - checkExistence, - createAndPopulateGroup, -} from '../../../helpers/api-integration/v2'; - -describe('POST /groups/:id/leave', () => { - context('user is not member of the group', () => { - it('returns an error'); - }); - - context('user is a non-leader member of a guild', () => { - let user, group; - - beforeEach(async () => { - let groupData = await createAndPopulateGroup({ - members: 3, - groupDetails: { - name: 'test guild', - type: 'guild', - privacy: 'public', - }, - }); - - user = groupData.members[0]; - group = groupData.group; - }); - - it('leaves the group', async () => { - await user.post(`/groups/${group._id}/leave`); - - await user.sync(); - - expect(user.guilds).to.not.include(group._id); - }); - }); - - context('user is the last member of a public guild', () => { - let user, group; - - beforeEach(async () => { - let groupData = await createAndPopulateGroup({ - groupDetails: { - name: 'test guild', - type: 'guild', - privacy: 'public', - }, - }); - - user = groupData.groupLeader; - group = groupData.group; - }); - - it('leaves the group accessible', async () => { - await user.post(`/groups/${group._id}/leave`); - - await expect(user.get(`/groups/${group._id}`)).to.eventually.have.property('_id', group._id); - }); - }); - - context('user is the last member of a private group', () => { - let user, group; - - beforeEach(async () => { - let groupData = await createAndPopulateGroup({ - groupDetails: { - name: 'test guild', - type: 'guild', - privacy: 'private', - }, - }); - - user = groupData.groupLeader; - group = groupData.group; - }); - - it('group is deleted', async () => { - await user.post(`/groups/${group._id}/leave`); - - await expect(checkExistence('groups', group._id)).to.eventually.eql(false); - }); - }); - - context('user is the last member of a private group with pending invites', () => { - let user, invitee1, invitee2, group; - - beforeEach(async () => { - let groupData = await createAndPopulateGroup({ - invites: 2, - groupDetails: { - name: 'test guild', - type: 'guild', - privacy: 'private', - }, - }); - - user = groupData.groupLeader; - group = groupData.group; - invitee1 = groupData.invitees[0]; - invitee2 = groupData.invitees[1]; - }); - - it('deletes the group invitations from users', async () => { - await user.post(`/groups/${group._id}/leave`); - - await expect(invitee1.get('/user')).to.eventually.have.deep.property('invitations.guilds').and.to.be.empty; - await expect(invitee2.get('/user')).to.eventually.have.deep.property('invitations.guilds').and.to.be.empty; - }); - }); - - context('user is the last member of a party with pending invites', () => { - let user, invitee1, invitee2, group; - - beforeEach(async () => { - let groupData = await createAndPopulateGroup({ - invites: 2, - groupDetails: { - name: 'test guild', - type: 'party', - privacy: 'private', - }, - }); - - user = groupData.groupLeader; - group = groupData.group; - invitee1 = groupData.invitees[0]; - invitee2 = groupData.invitees[1]; - }); - - it('deletes the group invitations from users', async () => { - await user.post(`/groups/${group._id}/leave`); - - await expect(invitee1.get('/user')).to.eventually.have.deep.property('invitations.party').and.to.be.empty; - await expect(invitee2.get('/user')).to.eventually.have.deep.property('invitations.party').and.to.be.empty; - }); - }); -}); diff --git a/test/api/v2/groups/POST-groups_id_removeMember.test.js b/test/api/v2/groups/POST-groups_id_removeMember.test.js deleted file mode 100644 index ccedd69e9d..0000000000 --- a/test/api/v2/groups/POST-groups_id_removeMember.test.js +++ /dev/null @@ -1,53 +0,0 @@ -import { - createAndPopulateGroup, - translate as t, -} from '../../../helpers/api-integration/v2'; - -describe('POST /groups/:id/removeMember', () => { - context('user is not member of the group', () => { - it('returns an error'); - }); - - context('user is a non-leader member of a guild', () => { - it('returns an error'); - }); - - context('user is the leader of a guild', () => { - let leader, member, group; - - beforeEach(async () => { - return createAndPopulateGroup({ - members: 1, - groupDetails: { - name: 'test group', - type: 'guild', - privacy: 'public', - }, - }).then((res) => { - leader = res.groupLeader; - member = res.members[0]; - group = res.group; - }); - }); - - it('does not allow leader to remove themselves', async () => { - return expect(leader.post(`/groups/${group._id}/removeMember`, null, { - uuid: leader._id, - })).to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageGroupCannotRemoveSelf'), - }); - }); - - it('can remove other members of guild', async () => { - return leader.post(`/groups/${group._id}/removeMember`, null, { - uuid: member._id, - }).then(() => { - return leader.get(`/groups/${group._id}`); - }).then((guild) => { - expect(guild.members).to.have.a.lengthOf(1); - expect(guild.members[0]._id).to.not.eql(member._id); - }); - }); - }); -}); diff --git a/test/api/v2/groups/chat/DELETE-groups_id_chat.test.js b/test/api/v2/groups/chat/DELETE-groups_id_chat.test.js deleted file mode 100644 index dc575b0ec6..0000000000 --- a/test/api/v2/groups/chat/DELETE-groups_id_chat.test.js +++ /dev/null @@ -1,39 +0,0 @@ -import { - createAndPopulateGroup, - translate as t, -} from '../../../../helpers/api-integration/v2'; - -describe('DELETE /groups/:id/chat', () => { - let group, message, user; - - beforeEach(async () => { - return createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - }).then((res) => { - group = res.group; - user = res.groupLeader; - - return user.post(`/groups/${group._id}/chat`, null, { message: 'Some message' }); - }).then((res) => { - message = res.message; - }); - }); - - it('deletes a message', async () => { - return user.del(`/groups/${group._id}/chat/${message.id}`).then(() => { - return user.get(`/groups/${group._id}/chat/`); - }).then((messages) => { - expect(messages).to.have.length(0); - }); - }); - - it('returns an error is message does not exist', async () => { - return expect(user.del(`/groups/${group._id}/chat/some-fake-id`)).to.eventually.be.rejected.and.eql({ - code: 404, - text: t('messageGroupChatNotFound'), - }); - }); -}); diff --git a/test/api/v2/groups/chat/GET-groups_id_chat.test.js b/test/api/v2/groups/chat/GET-groups_id_chat.test.js deleted file mode 100644 index d6dc691af7..0000000000 --- a/test/api/v2/groups/chat/GET-groups_id_chat.test.js +++ /dev/null @@ -1,38 +0,0 @@ -import { - createAndPopulateGroup, -} from '../../../../helpers/api-integration/v2'; - -describe('GET /groups/:id/chat', () => { - context('group with multiple messages', () => { - let group, member, user; - - beforeEach(async () => { - let groupData = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - members: 1, - }); - - group = groupData.group; - user = groupData.groupLeader; - member = groupData.members[0]; - - await member.post(`/groups/${group._id}/chat`, null, { message: 'Group member message' }); - await user.post(`/groups/${group._id}/chat`, null, { message: 'User message' }); - }); - - it('gets messages', async () => { - let messages = await user.get(`/groups/${group._id}/chat`); - - expect(messages).to.have.length(2); - - let message = messages[0]; - - expect(message.id).to.exist; - expect(message.text).to.exist; - expect(message.uuid).to.exist; - }); - }); -}); diff --git a/test/api/v2/groups/chat/POST-groups_id_chat.test.js b/test/api/v2/groups/chat/POST-groups_id_chat.test.js deleted file mode 100644 index cc280077aa..0000000000 --- a/test/api/v2/groups/chat/POST-groups_id_chat.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { - createAndPopulateGroup, - translate as t, -} from '../../../../helpers/api-integration/v2'; - -describe('POST /groups/:id/chat', () => { - let group, user; - - beforeEach(async () => { - return createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - }).then((res) => { - group = res.group; - user = res.groupLeader; - }); - }); - - it('creates a chat message', async () => { - return user.post(`/groups/${group._id}/chat`, null, { - message: 'Test Message', - }).then((res) => { - let message = res.message; - - expect(message.id).to.exist; - expect(message.timestamp).to.exist; - expect(message.text).to.eql('Test Message'); - expect(message.uuid).to.eql(user._id); - }); - }); - - it('does not post an empty message', async () => { - return expect(user.post(`/groups/${group._id}/chat`, null, { - message: '', - })).to.eventually.be.rejected.and.eql({ - code: 400, - text: t('messageGroupChatBlankMessage'), - }); - }); -}); diff --git a/test/api/v2/groups/chat/POST-groups_id_chat_id_clearflags.test.js b/test/api/v2/groups/chat/POST-groups_id_chat_id_clearflags.test.js deleted file mode 100644 index 0b6a54c429..0000000000 --- a/test/api/v2/groups/chat/POST-groups_id_chat_id_clearflags.test.js +++ /dev/null @@ -1,130 +0,0 @@ -import { - createAndPopulateGroup, - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v2'; - -describe('POST /groups/:id/chat/:id/clearflags', () => { - let guild; - - beforeEach(async () => { - let { group } = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - members: 1, - chat: [{ - id: 'message-to-clear', - flagCount: 1, - flags: { 'some-id': true }, - }], - }, - }); - - guild = group; - }); - - context('non admin', () => { - let nonadmin; - - beforeEach(async () => { - nonadmin = await generateUser(); - }); - - it('cannot clear flags', async () => { - return expect(nonadmin.post(`/groups/${guild._id}/chat/message-to-clear/clearflags`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageGroupChatAdminClearFlagCount'), - }); - }); - }); - - context('admin', () => { - let admin; - - beforeEach(async () => { - return generateUser({ - 'contributor.admin': true, - }).then((user) => { - admin = user; - }); - }); - - it('clears flags', async () => { - return admin.post(`/groups/${guild._id}/chat/message-to-clear/clearflags`).then(() => { - return admin.get(`/groups/${guild._id}/chat`); - }).then((messages) => { - expect(messages[0].flagCount).to.eql(0); - }); - }); - - it('leaves old flags on the flag object', async () => { - return admin.post(`/groups/${guild._id}/chat/message-to-clear/clearflags`).then(() => { - return admin.get(`/groups/${guild._id}/chat`); - }).then((messages) => { - expect(messages[0].flags).to.have.property('some-id', true); - }); - }); - - it('returns error if message does not exist', async () => { - return expect(admin.post(`/groups/${guild._id}/chat/non-existant-message/clearflags`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - text: t('messageGroupChatNotFound'), - }); - }); - }); - - context('admin user, group with multiple messages', () => { - let admin, author, groupWithMessages; - - beforeEach(async () => { - author = await generateUser(); - admin = await generateUser({ - 'contributor.admin': true, - }); - - let groupData = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - chat: [ - { id: 'message-to-unflag', uuid: author._id, flagCount: 1, flags: {'some-user': true} }, - { id: '1-flag-message', uuid: author._id, flagCount: 1, flags: { id1: true } }, - { id: '2-flag-message', uuid: author._id, flagCount: 2, flags: { id1: true, id2: true } }, - { id: 'no-flags', uuid: author._id, flagCount: 0, flags: {} }, - ], - }, - members: 1, - }); - - groupWithMessages = groupData.group; - }); - - it('changes only the message that is flagged', async () => { - return admin.post(`/groups/${groupWithMessages._id}/chat/message-to-unflag/clearflags`).then(() => { - return admin.get(`/groups/${groupWithMessages._id}/chat`); - }).then((messages) => { - expect(messages).to.have.lengthOf(4); - - let messageThatWasUnflagged = messages[0]; - let messageWith1Flag = messages[1]; - let messageWith2Flag = messages[2]; - let messageWithoutFlags = messages[3]; - - expect(messageThatWasUnflagged.flagCount).to.eql(0); - expect(messageThatWasUnflagged.flags).to.have.property('some-user', true); - - expect(messageWith1Flag.flagCount).to.eql(1); - expect(messageWith1Flag.flags).to.have.property('id1', true); - - expect(messageWith2Flag.flagCount).to.eql(2); - expect(messageWith2Flag.flags).to.have.property('id1', true); - - expect(messageWithoutFlags.flagCount).to.eql(0); - expect(messageWithoutFlags.flags).to.eql({}); - }); - }); - }); -}); diff --git a/test/api/v2/groups/chat/POST-groups_id_chat_id_flag.test.js b/test/api/v2/groups/chat/POST-groups_id_chat_id_flag.test.js deleted file mode 100644 index cd813061b2..0000000000 --- a/test/api/v2/groups/chat/POST-groups_id_chat_id_flag.test.js +++ /dev/null @@ -1,186 +0,0 @@ -import { - createAndPopulateGroup, - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v2'; - -describe('POST /groups/:id/chat/:id/flag', () => { - context('another member\'s message', () => { - let group, member, message, user; - - beforeEach(async () => { - return createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - members: 1, - }).then((res) => { - group = res.group; - user = res.groupLeader; - member = res.members[0]; - - return member.post(`/groups/${group._id}/chat`, null, { message: 'Group member message' }); - }).then((res) => { - message = res.message; - }); - }); - - it('flags message', async () => { - return user.post(`/groups/${group._id}/chat/${message.id}/flag`).then(() => { - return user.get(`/groups/${group._id}/chat`); - }).then((messages) => { - expect(messages[0].flagCount).to.eql(1); - }); - }); - - it('cannot flag the same message twice', async () => { - return expect(user.post(`/groups/${group._id}/chat/${message.id}/flag`).then(() => { - return user.post(`/groups/${group._id}/chat/${message.id}/flag`); - })).to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageGroupChatFlagAlreadyReported'), - }); - }); - }); - - context('own message', () => { - let group, message, user; - - beforeEach(async () => { - return createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - members: 1, - }, - }).then((res) => { - group = res.group; - user = res.groupLeader; - - return user.post(`/groups/${group._id}/chat`, null, { message: 'User\'s own message' }); - }).then((res) => { - message = res.message; - }); - }); - - it('cannot flag message', async () => { - return expect(user.post(`/groups/${group._id}/chat/${message.id}/flag`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageGroupChatFlagOwnMessage'), - }); - }); - }); - - context('nonexistant message', () => { - let group, user; - - beforeEach(async () => { - return createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - }).then((res) => { - group = res.group; - user = res.groupLeader; - }); - }); - - it('returns error', async () => { - return expect(user.post(`/groups/${group._id}/chat/non-existant-message/flag`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - text: t('messageGroupChatNotFound'), - }); - }); - }); - - context('group with multiple messages', () => { - let admin, author, group, user; - - beforeEach(async () => { - author = await generateUser(); - admin = await generateUser({ - 'contributor.admin': true, - }); - - let groupData = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - chat: [ - { id: 'message-to-be-flagged', uuid: author._id, flagCount: 0, flags: {} }, - { id: '1-flag-message', uuid: author._id, flagCount: 1, flags: { id1: true } }, - { id: '2-flag-message', uuid: author._id, flagCount: 2, flags: { id1: true, id2: true } }, - { id: 'no-flags', uuid: author._id, flagCount: 0, flags: {} }, - ], - }, - members: 1, - }); - - group = groupData.group; - user = groupData.groupLeader; - }); - - it('changes only the message that is flagged', async () => { - return user.post(`/groups/${group._id}/chat/message-to-be-flagged/flag`).then(() => { - return admin.get(`/groups/${group._id}/chat`); - }).then((messages) => { - expect(messages).to.have.lengthOf(4); - - let messageThatWasFlagged = messages[0]; - let messageWith1Flag = messages[1]; - let messageWith2Flag = messages[2]; - let messageWithoutFlags = messages[3]; - - expect(messageThatWasFlagged.flagCount).to.eql(1); - expect(messageThatWasFlagged.flags).to.have.property(user._id, true); - - expect(messageWith1Flag.flagCount).to.eql(1); - expect(messageWith1Flag.flags).to.have.property('id1', true); - - expect(messageWith2Flag.flagCount).to.eql(2); - expect(messageWith2Flag.flags).to.have.property('id1', true); - - expect(messageWithoutFlags.flagCount).to.eql(0); - expect(messageWithoutFlags.flags).to.eql({}); - }); - }); - }); - - context('admin flagging a message', () => { - let group, member, message, user; - - beforeEach(async () => { - return createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - leaderDetails: { - 'contributor.admin': true, - balance: 10, - }, - members: 1, - }).then((res) => { - group = res.group; - user = res.groupLeader; - member = res.members[0]; - - return member.post(`/groups/${group._id}/chat`, null, { message: 'Group member message' }); - }).then((res) => { - message = res.message; - }); - }); - - it('sets flagCount to 5', async () => { - return user.post(`/groups/${group._id}/chat/${message.id}/flag`).then(() => { - return user.get(`/groups/${group._id}/chat`); - }).then((messages) => { - expect(messages[0].flagCount).to.eql(5); - }); - }); - }); -}); diff --git a/test/api/v2/groups/chat/POST-groups_id_chat_id_like.test.js b/test/api/v2/groups/chat/POST-groups_id_chat_id_like.test.js deleted file mode 100644 index 9c8ff4c586..0000000000 --- a/test/api/v2/groups/chat/POST-groups_id_chat_id_like.test.js +++ /dev/null @@ -1,149 +0,0 @@ -import { - createAndPopulateGroup, - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v2'; - -describe('POST /groups/:id/chat/:id/like', () => { - context('another member\'s message', () => { - let group, member, message, user; - - beforeEach(async () => { - return createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - members: 1, - }).then((res) => { - group = res.group; - user = res.groupLeader; - member = res.members[0]; - - return member.post(`/groups/${group._id}/chat`, null, { message: 'Group member message' }); - }).then((res) => { - message = res.message; - }); - }); - - it('likes message', async () => { - return user.post(`/groups/${group._id}/chat/${message.id}/like`).then((messages) => { - expect(messages[0].likes[user._id]).to.eql(true); - }); - }); - - it('returns the message object', async () => { - return user.post(`/groups/${group._id}/chat/${message.id}/like`).then((messages) => { - expect(messages[0].text).to.eql('Group member message'); - expect(messages[0].uuid).to.eql(member._id); - expect(messages[0].user).to.eql(member.profile.name); - }); - }); - }); - - context('own message', () => { - let group, message, user; - - beforeEach(async () => { - return createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - members: 1, - }, - }).then((res) => { - group = res.group; - user = res.groupLeader; - - return user.post(`/groups/${group._id}/chat`, null, { message: 'User\'s own message' }); - }).then((res) => { - message = res.message; - }); - }); - - it('cannot like message', async () => { - return expect(user.post(`/groups/${group._id}/chat/${message.id}/like`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageGroupChatLikeOwnMessage'), - }); - }); - }); - - context('group with multiple messages', () => { - let admin, author, group, user; - - beforeEach(async () => { - author = await generateUser(); - admin = await generateUser({ - 'contributor.admin': true, - }); - - let groupData = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - chat: [ - { id: 'message-to-be-liked', likes: {}, uuid: author._id, flagCount: 0, flags: {} }, - { id: '1-like-message', likes: { id: true }, uuid: author._id, flagCount: 1, flags: { id1: true } }, - { id: '2-like-message', likes: { id: true, id2: true }, uuid: author._id, flagCount: 2, flags: { id1: true, id2: true } }, - { id: 'no-likes', likes: {}, uuid: author._id, flagCount: 0, flags: {} }, - ], - }, - members: 1, - }); - - group = groupData.group; - user = groupData.groupLeader; - }); - - it('changes only the message that is liked', async () => { - return user.post(`/groups/${group._id}/chat/message-to-be-liked/like`).then(() => { - return admin.get(`/groups/${group._id}/chat`); - }).then((messages) => { - expect(messages).to.have.lengthOf(4); - - let messageThatWasLiked = messages[0]; - let messageWith1Like = messages[1]; - let messageWith2Like = messages[2]; - let messageWithoutLike = messages[3]; - - expect(messageThatWasLiked.likes).to.have.property(user._id, true); - - expect(messageWith1Like.flagCount).to.eql(1); - expect(messageWith1Like.flags).to.have.property('id1', true); - - expect(messageWith2Like.flagCount).to.eql(2); - expect(messageWith2Like.flags).to.have.property('id1', true); - expect(messageWith2Like.flags).to.have.property('id2', true); - - expect(messageWithoutLike.flagCount).to.eql(0); - expect(messageWithoutLike.flags).to.eql({}); - }); - }); - }); - - context('nonexistant message', () => { - let group, user; - - beforeEach(async () => { - return createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - }).then((res) => { - group = res.group; - user = res.groupLeader; - }); - }); - - it('returns error', async () => { - return expect(user.post(`/groups/${group._id}/chat/non-existant-message/like`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - text: t('messageGroupChatNotFound'), - }); - }); - }); -}); diff --git a/test/api/v2/members/POST-members_id_gift.test.js b/test/api/v2/members/POST-members_id_gift.test.js deleted file mode 100644 index d36306a79d..0000000000 --- a/test/api/v2/members/POST-members_id_gift.test.js +++ /dev/null @@ -1,61 +0,0 @@ -import { - generateUser, -} from '../../../helpers/api-integration/v2'; - -describe('POST /members/id/gift', () => { - let userWithBalance, userWithoutBalance; - - beforeEach(async () => { - userWithBalance = await generateUser({ balance: 10 }); - userWithoutBalance = await generateUser({ balance: 0 }); - }); - - context('send gems from balance', () => { - it('subtracts gems from sender\'s balance and adds it to recipient\'s balance', async () => { - await userWithBalance.post(`/members/${userWithoutBalance._id}/gift`, { - type: 'gems', - gems: { - amount: 1, - }, - }); - - await Promise.all([ - userWithoutBalance.sync(), - userWithBalance.sync(), - ]); - - expect(userWithBalance.balance).to.eql(9.75); - expect(userWithoutBalance.balance).to.eql(0.25); - }); - - it('adds a message to sender\'s inbox', async () => { - expect(userWithBalance.inbox.messages).to.be.empty; - - await userWithBalance.post(`/members/${userWithoutBalance._id}/gift`, { - type: 'gems', - gems: { - amount: 1, - }, - }); - - await userWithBalance.sync(); - - expect(userWithBalance.inbox.messages).to.not.be.empty; - }); - - it('adds a message to recipients\'s inbox', async () => { - expect(userWithoutBalance.inbox.messages).to.be.empty; - - await userWithBalance.post(`/members/${userWithoutBalance._id}/gift`, { - type: 'gems', - gems: { - amount: 1, - }, - }); - - await userWithoutBalance.sync(); - - expect(userWithoutBalance.inbox.messages).to.not.be.empty; - }); - }); -}); diff --git a/test/api/v2/members/POST-members_id_message.test.js b/test/api/v2/members/POST-members_id_message.test.js deleted file mode 100644 index ffbc02266a..0000000000 --- a/test/api/v2/members/POST-members_id_message.test.js +++ /dev/null @@ -1,70 +0,0 @@ -import { - generateUser, -} from '../../../helpers/api-integration/v2'; - -describe('POST /members/id/message', () => { - let sender, recipient; - - beforeEach(async () => { - sender = await generateUser(); - recipient = await generateUser(); - }); - - it('adds the sent message to sender\'s inbox', async () => { - expect(sender.inbox.messages).to.be.empty; - - await sender.post(`/members/${recipient._id}/message`, { - message: 'hello frodo', - }); - - await sender.sync(); - - expect(sender.inbox.messages).to.not.be.empty; - - let messageKey = Object.keys(sender.inbox.messages)[0]; - let message = sender.inbox.messages[messageKey]; - - expect(message.text).to.eql('hello frodo'); - }); - - it('adds a message to recipients\'s inbox', async () => { - expect(recipient.inbox.messages).to.be.empty; - - await sender.post(`/members/${recipient._id}/message`, { - message: 'hello frodo', - }); - - await recipient.sync(); - - expect(recipient.inbox.messages).to.not.be.empty; - - let messageKey = Object.keys(recipient.inbox.messages)[0]; - let message = recipient.inbox.messages[messageKey]; - - expect(message.text).to.eql('hello frodo'); - }); - - it('does not increment the sender\'s new messages field', async () => { - expect(sender.inbox.messages).to.be.empty; - - await sender.post(`/members/${recipient._id}/message`, { - message: 'hello frodo', - }); - - await sender.sync(); - - expect(sender.inbox.newMessages).to.eql(0); - }); - - it('increments the recipient\'s new messages field', async () => { - expect(recipient.inbox.messages).to.be.empty; - - await sender.post(`/members/${recipient._id}/message`, { - message: 'hello frodo', - }); - - await recipient.sync(); - - expect(recipient.inbox.newMessages).to.eql(1); - }); -}); diff --git a/test/api/v2/register/POST-register.test.js b/test/api/v2/register/POST-register.test.js deleted file mode 100644 index 48981c55b2..0000000000 --- a/test/api/v2/register/POST-register.test.js +++ /dev/null @@ -1,293 +0,0 @@ -import { - generateUser, - requester, - translate as t, -} from '../../../helpers/api-integration/v2'; -import { v4 as generateRandomUserName } from 'uuid'; -import { each } from 'lodash'; - -describe('POST /register', () => { - context('username and email are free', () => { - it('registers a new user', async () => { - let api = requester(); - let username = generateRandomUserName(); - let email = `${username}@example.com`; - let password = 'password'; - - return api.post('/register', { - username, - email, - password, - confirmPassword: password, - }).then((user) => { - expect(user._id).to.exist; - expect(user.apiToken).to.exist; - expect(user.auth.local.username).to.eql(username); - }); - }); - - it('requires password and confirmPassword to match', async () => { - let api = requester(); - let username = generateRandomUserName(); - let email = `${username}@example.com`; - let password = 'password'; - let confirmPassword = 'not password'; - - return expect(api.post('/register', { - username, - email, - password, - confirmPassword, - })).to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageAuthPasswordMustMatch'), - }); - }); - - it('requires a username', async () => { - let api = requester(); - let email = `${generateRandomUserName()}@example.com`; - let password = 'password'; - let confirmPassword = 'password'; - - return expect(api.post('/register', { - email, - password, - confirmPassword, - })).to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageAuthCredentialsRequired'), - }); - }); - - it('requires an email', async () => { - let api = requester(); - let username = generateRandomUserName(); - let password = 'password'; - let confirmPassword = 'password'; - - return expect(api.post('/register', { - username, - password, - confirmPassword, - })).to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageAuthCredentialsRequired'), - }); - }); - - it('requires a password', async () => { - let api = requester(); - let username = generateRandomUserName(); - let email = `${username}@example.com`; - let confirmPassword = 'password'; - - return expect(api.post('/register', { - username, - email, - confirmPassword, - })).to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageAuthCredentialsRequired'), - }); - }); - }); - - context('login is already taken', () => { - let username, email; - beforeEach(async () => { - username = generateRandomUserName(); - email = `${username}@example.com`; - return generateUser({ - 'auth.local.username': username, - 'auth.local.lowerCaseUsername': username, - 'auth.local.email': email, - }); - }); - - it('rejects if username is already taken', async () => { - let api = requester(); - let uniqueEmail = `${generateRandomUserName()}@exampe.com`; - let password = 'password'; - - return expect(api.post('/register', { - username, - email: uniqueEmail, - password, - confirmPassword: password, - })).to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageAuthUsernameTaken'), - }); - }); - - it('rejects if email is already taken', async () => { - let api = requester(); - let uniqueUsername = generateRandomUserName(); - let password = 'password'; - - return expect(api.post('/register', { - username: uniqueUsername, - email, - password, - confirmPassword: password, - })).to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageAuthEmailTaken'), - }); - }); - }); - - context('successful login via api', () => { - let api, username, email, password; - - beforeEach(async () => { - api = requester(); - username = generateRandomUserName(); - email = `${username}@example.com`; - password = 'password'; - }); - - it('sets all site tour values to -2 (already seen)', async () => { - return api.post('/register', { - username, - email, - password, - confirmPassword: password, - }).then((user) => { - expect(user.flags.tour).to.not.be.empty; - - each(user.flags.tour, (value) => { - expect(value).to.eql(-2); - }); - }); - }); - - it('populates user with default todos, not no other task types', async () => { - return api.post('/register', { - username, - email, - password, - confirmPassword: password, - }).then((user) => { - expect(user.todos).to.not.be.empty; - expect(user.dailys).to.be.empty; - expect(user.habits).to.be.empty; - expect(user.rewards).to.be.empty; - }); - }); - - it('populates user with default tags', async () => { - return api.post('/register', { - username, - email, - password, - confirmPassword: password, - }).then((user) => { - expect(user.tags).to.not.be.empty; - }); - }); - }); - - context('successful login with habitica-web header', () => { - let api, username, email, password; - - beforeEach(async () => { - api = requester({}, {'x-client': 'habitica-web'}); - username = generateRandomUserName(); - email = `${username}@example.com`; - password = 'password'; - }); - - it('sets all common tutorial flags to true', async () => { - return api.post('/register', { - username, - email, - password, - confirmPassword: password, - }).then((user) => { - expect(user.flags.tour).to.not.be.empty; - - each(user.flags.tutorial.common, (value) => { - expect(value).to.eql(true); - }); - }); - }); - - it('populates user with default todos, habits, and rewards', async () => { - return api.post('/register', { - username, - email, - password, - confirmPassword: password, - }).then((user) => { - expect(user.todos).to.not.be.empty; - expect(user.dailys).to.be.empty; - expect(user.habits).to.not.be.empty; - expect(user.rewards).to.not.be.empty; - }); - }); - - it('populates user with default tags', async () => { - return api.post('/register', { - username, - email, - password, - confirmPassword: password, - }).then((user) => { - expect(user.tags).to.not.be.empty; - }); - }); - }); - - context('successful login with habitica-android header', () => { - let api, username, email, password; - - beforeEach(async () => { - api = requester({}, {'x-client': 'habitica-android'}); - username = generateRandomUserName(); - email = `${username}@example.com`; - password = 'password'; - }); - - it('sets all common tutorial flags to true', async () => { - return api.post('/register', { - username, - email, - password, - confirmPassword: password, - }).then((user) => { - expect(user.flags.tour).to.not.be.empty; - - each(user.flags.tutorial.common, (value) => { - expect(value).to.eql(true); - }); - }); - }); - - it('populates user with default todos, habits, and rewards', async () => { - return api.post('/register', { - username, - email, - password, - confirmPassword: password, - }).then((user) => { - expect(user.todos).to.not.be.empty; - expect(user.dailys).to.be.empty; - expect(user.habits).to.not.be.empty; - expect(user.rewards).to.not.be.empty; - }); - }); - - it('populates user with default tags', async () => { - return api.post('/register', { - username, - email, - password, - confirmPassword: password, - }).then((user) => { - expect(user.tags).to.not.be.empty; - }); - }); - }); -}); diff --git a/test/api/v2/status/GET-status.test.js b/test/api/v2/status/GET-status.test.js deleted file mode 100644 index 6aac4346a1..0000000000 --- a/test/api/v2/status/GET-status.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import {requester} from '../../../helpers/api-integration/v2'; - -describe('Status', () => { - it('returns a status of up when server is up', async () => { - let api = requester(); - - await expect(api.get('/status')).to.eventually.eql({status: 'up'}); - }); -}); diff --git a/test/api/v2/user/DELETE-user.test.js b/test/api/v2/user/DELETE-user.test.js deleted file mode 100644 index 8d28a07b78..0000000000 --- a/test/api/v2/user/DELETE-user.test.js +++ /dev/null @@ -1,186 +0,0 @@ -import { - checkExistence, - createAndPopulateGroup, - generateGroup, - generateUser, -} from '../../../helpers/api-integration/v2'; -import { - find, - map, -} from 'lodash'; -import Bluebird from 'bluebird'; - -describe('DELETE /user', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('deletes the user', async () => { - return expect(user.del('/user').then(() => { - return checkExistence('users', user._id); - })).to.eventually.eql(false); - }); - - it('deletes the user\'s tasks', async () => { - // gets the user's todos ids - let ids = user.todos.map(todo => todo._id); - expect(ids.length).to.be.above(0); // make sure the user has some task to delete - - await user.del('/user'); - - await Bluebird.all(map(ids, id => { - return expect(checkExistence('tasks', id)).to.eventually.eql(false); - })); - }); - - context('user has active subscription', () => { - it('does not delete account'); - }); - - context('last member of a party', () => { - let party; - - beforeEach(async () => { - return generateGroup(user, { - type: 'party', - privacy: 'private', - }).then((group) => { - party = group; - }); - }); - - it('deletes party when user is the only member', async () => { - await user.del('/user'); - await expect(checkExistence('groups', party._id)).to.eventually.eql(false); - }); - }); - - context('last member of a private guild', () => { - let guild, lastMember; - - beforeEach(async () => { - let { - groupLeader, - group, - } = await createAndPopulateGroup({ - type: 'guild', - privacy: 'private', - }); - - guild = group; - lastMember = groupLeader; - }); - - it('deletes guild when user is the only member', async () => { - await lastMember.del('/user'); - await expect(checkExistence('groups', guild._id)).to.eventually.eql(false); - }); - }); - - context('groups user is leader of', () => { - let group, oldLeader, newLeader; - - beforeEach(async () => { - return createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - members: 3, - }).then((res) => { - group = res.group; - newLeader = res.members[0]; - oldLeader = res.groupLeader; - }); - }); - - it('chooses new group leader for any group user was the leader of', async () => { - return oldLeader.del('/user').then(() => { - return newLeader.get(`/groups/${group._id}`); - }).then((guild) => { - expect(guild.leader).to.exist; - expect(guild.leader._id).to.not.eql(oldLeader._id); - }); - }); - }); - - context('groups user is a part of', () => { - let group1, group2, userToDelete, otherUser; - - beforeEach(async () => { - return generateUser({ - balance: 10, - }).then((_user) => { - userToDelete = _user; - - return generateGroup(userToDelete, { - type: 'guild', - privacy: 'public', - }); - }).then((newGroup) => { - group1 = newGroup; - - return createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - members: 3, - }); - }).then((res) => { - group2 = res.group; - otherUser = res.members[0]; - - return userToDelete.post(`/groups/${group2._id}/join`); - }); - }); - - it('removes user from all groups user was a part of', async () => { - return userToDelete.del('/user').then(() => { - return otherUser.get(`/groups/${group1._id}`); - }).then((fetchedGroup1) => { - expect(fetchedGroup1.members).to.be.empty; - - return otherUser.get(`/groups/${group2._id}`); - }).then((fetchedGroup2) => { - expect(fetchedGroup2.members).to.not.be.empty; - - let userInGroup = find(fetchedGroup2.members, (member) => { - return member._id === userToDelete._id; - }); - - expect(userInGroup).to.not.be.ok; - }); - }); - }); - - context('pending invitation to group', () => { - let group, userToDelete, otherUser; - - beforeEach(async () => { - return createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - members: 3, - invites: 2, - }).then((res) => { - group = res.group; - otherUser = res.members[0]; - userToDelete = res.invitees[0]; - }); - }); - - it('removes invitations from groups', async () => { - return userToDelete.del('/user').then(() => { - return otherUser.get(`/groups/${group._id}`); - }).then((fetchedGroup) => { - expect(fetchedGroup.invites).to.have.a.lengthOf(1); - expect(fetchedGroup.invites[0]._id).to.not.eql(userToDelete._id); - }); - }); - }); -}); diff --git a/test/api/v2/user/GET-user.test.js b/test/api/v2/user/GET-user.test.js deleted file mode 100644 index c1e22d020a..0000000000 --- a/test/api/v2/user/GET-user.test.js +++ /dev/null @@ -1,28 +0,0 @@ -import { - generateUser, -} from '../../../helpers/api-integration/v2'; - -describe('GET /user', () => { - let user; - - before(async () => { - let usr = await generateUser(); - user = await usr.get('/user'); - }); - - it('gets the user object', async () => { - expect(user._id).to.eql(user._id); - expect(user.auth.local.username).to.eql(user.auth.local.username); - expect(user.todos).to.eql(user.todos); - expect(user.items).to.eql(user.items); - }); - - it('does not include password information', async () => { - expect(user.auth.local.hashed_password).to.not.exist; - expect(user.auth.local.salt).to.not.exist; - }); - - it('does not include api token', async () => { - expect(user.apiToken).to.not.exist; - }); -}); diff --git a/test/api/v2/user/GET-user_tags.test.js b/test/api/v2/user/GET-user_tags.test.js deleted file mode 100644 index fd2032dc96..0000000000 --- a/test/api/v2/user/GET-user_tags.test.js +++ /dev/null @@ -1,16 +0,0 @@ -import { - generateUser, -} from '../../../helpers/api-integration/v2'; - -describe('GET /user/tags', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('gets the user\'s tags', async () => { - return expect(user.get('/user/tags')) - .to.eventually.eql(user.tags); - }); -}); diff --git a/test/api/v2/user/GET-user_tags_id.test.js b/test/api/v2/user/GET-user_tags_id.test.js deleted file mode 100644 index 9c774c84b6..0000000000 --- a/test/api/v2/user/GET-user_tags_id.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../helpers/api-integration/v2'; - -describe('GET /user/tags/id', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('gets a user\'s tag by id', async () => { - return expect(user.get(`/user/tags/${user.tags[0].id}`)) - .to.eventually.eql(user.tags[0]); - }); - - it('fails for non-existent tags', async () => { - return expect(user.get('/user/tags/not-an-id')) - .to.eventually.be.rejected.and.eql({ - code: 404, - text: t('messageTagNotFound'), - }); - }); -}); diff --git a/test/api/v2/user/PUT-user.test.js b/test/api/v2/user/PUT-user.test.js deleted file mode 100644 index e4774528ae..0000000000 --- a/test/api/v2/user/PUT-user.test.js +++ /dev/null @@ -1,201 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../helpers/api-integration/v2'; - -import { each, get } from 'lodash'; - -describe('PUT /user', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - context('Allowed Operations', () => { - it('updates the user', async () => { - await user.put('/user', { - 'profile.name': 'Frodo', - 'preferences.costume': true, - 'stats.hp': 14, - }); - - await user.sync(); - - expect(user.profile.name).to.eql('Frodo'); - expect(user.preferences.costume).to.eql(true); - expect(user.stats.hp).to.eql(14); - }); - }); - - context('Top Level Protected Operations', () => { - let protectedOperations = { - 'gem balance': {balance: 100}, - auth: {'auth.blocked': true, 'auth.timestamps.created': new Date()}, - contributor: {'contributor.level': 9, 'contributor.admin': true, 'contributor.text': 'some text'}, - backer: {'backer.tier': 10, 'backer.npc': 'Bilbo'}, - subscriptions: {'purchased.plan.extraMonths': 500, 'purchased.plan.consecutive.trinkets': 1000}, - 'customization gem purchases': {'purchased.background.tavern': true, 'purchased.skin.bear': true}, - tasks: {todos: [], habits: [], dailys: [], rewards: []}, - }; - - each(protectedOperations, (data, testName) => { - it(`does not allow updating ${testName}`, async () => { - let errorText = []; - each(data, (value, operation) => { - errorText.push(t('messageUserOperationProtected', { operation })); - }); - - await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({ - code: 401, - text: errorText, - }); - }); - }); - }); - - context('Sub-Level Protected Operations', () => { - let protectedOperations = { - 'class stat': {'stats.class': 'wizard'}, - }; - - each(protectedOperations, (data, testName) => { - it(`does not allow updating ${testName}`, async () => { - let errorText = []; - each(data, (value, operation) => { - errorText.push(t('messageUserOperationProtected', { operation })); - }); - - await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({ - code: 401, - text: errorText, - }); - }); - }); - }); - - context('Default Appearance Preferences', () => { - let testCases = { - shirt: 'yellow', - skin: 'ddc994', - 'hair.color': 'blond', - 'hair.bangs': 2, - 'hair.base': 1, - 'hair.flower': 4, - size: 'broad', - }; - - each(testCases, (item, type) => { - const update = {}; - update[`preferences.${type}`] = item; - - it(`updates user with ${type} that is a default`, async () => { - let dbUpdate = {}; - dbUpdate[`purchased.${type}.${item}`] = true; - await user.update(dbUpdate); - - // Sanity checks to make sure user is not already equipped with item - expect(get(user.preferences, type)).to.not.eql(item); - - let updatedUser = await user.put('/user', update); - - expect(get(updatedUser.preferences, type)).to.eql(item); - }); - }); - - it('returns an error if user tries to update body size with invalid type', async () => { - await expect(user.put('/user', { - 'preferences.size': 'round', - })).to.eventually.be.rejected.and.eql({ - code: 401, - text: ['Must purchase round to set it on preferences.size'], - }); - }); - - it('can set beard to default', async () => { - await user.update({ - 'purchased.hair.beard': 3, - 'preferences.hair.beard': 3, - }); - - let updatedUser = await user.put('/user', { - 'preferences.hair.beard': 0, - }); - - expect(updatedUser.preferences.hair.beard).to.eql(0); - }); - - it('can set mustache to default', async () => { - await user.update({ - 'purchased.hair.mustache': 2, - 'preferences.hair.mustache': 2, - }); - - let updatedUser = await user.put('/user', { - 'preferences.hair.mustache': 0, - }); - - expect(updatedUser.preferences.hair.mustache).to.eql(0); - }); - }); - - context('Purchasable Appearance Preferences', () => { - let testCases = { - background: 'volcano', - shirt: 'convict', - skin: 'cactus', - 'hair.base': 7, - 'hair.beard': 2, - 'hair.color': 'rainbow', - 'hair.mustache': 2, - }; - - each(testCases, (item, type) => { - const update = {}; - update[`preferences.${type}`] = item; - - it(`returns an error if user tries to update ${type} with ${type} the user does not own`, async () => { - await expect(user.put('/user', update)).to.eventually.be.rejected.and.eql({ - code: 401, - text: [`Must purchase ${item} to set it on preferences.${type}`], - }); - }); - - it(`updates user with ${type} user does own`, async () => { - let dbUpdate = {}; - dbUpdate[`purchased.${type}.${item}`] = true; - await user.update(dbUpdate); - - // Sanity check to make sure user is not already equipped with item - expect(get(user.preferences, type)).to.not.eql(item); - - let updatedUser = await user.put('/user', update); - - expect(get(updatedUser.preferences, type)).to.eql(item); - }); - }); - }); - - context('Improvement Categories', () => { - it('sets valid categories', async () => { - await user.put('/user', { - 'preferences.improvementCategories': ['work', 'school'], - }); - - await user.sync(); - - expect(user.preferences.improvementCategories).to.eql(['work', 'school']); - }); - - it('discards invalid categories', async () => { - await expect(user.put('/user', { - 'preferences.improvementCategories': ['work', 'procrastination', 'school'], - })).to.eventually.be.rejected.and.eql({ - code: 400, - text: [ - 'Validator failed for path `preferences.improvementCategories` with value `work,procrastination,school`', - ], - }); - }); - }); -}); diff --git a/test/api/v2/user/anonymized/GET-user_anonymized.test.js b/test/api/v2/user/anonymized/GET-user_anonymized.test.js deleted file mode 100644 index 3bfb4d1dcb..0000000000 --- a/test/api/v2/user/anonymized/GET-user_anonymized.test.js +++ /dev/null @@ -1,98 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v2'; -import { each } from 'lodash'; - -describe('GET /user/anonymized', () => { - let user, anonymizedUser; - - before(async () => { - user = await generateUser({ - 'inbox.messages': { - 'the-message-id': { - sort: 214, - user: 'Some user', - backer: {}, - contributor: { - text: 'Blacksmith', - level: 2, - contributions: 'Made some contributions', - admin: false, - }, - uuid: 'some-users-uuid', - flagCount: 0, - flags: {}, - likes: {}, - timestamp: 1444154258699.0000000000000000, - text: 'Lorem ipsum', - id: 'the-messages-id', - sent: true, - }, - }, - }); - - await user.post('/user/tasks', { - text: 'some private text', - notes: 'some private notes', - checklist: [ - {text: 'a private checklist'}, - {text: 'another private checklist'}, - ], - type: 'daily', - }); - - anonymizedUser = await user.get('/user/anonymized'); - }); - - it('retains user id', async () => { - expect(anonymizedUser._id).to.eql(user._id); - }); - - it('removes credentials and financial information', async () => { - expect(anonymizedUser.apiToken).to.not.exist; - expect(anonymizedUser.auth.local).to.not.exist; - expect(anonymizedUser.auth.facebook).to.not.exist; - expect(anonymizedUser.purchased.plan).to.not.exist; - }); - - it('removes profile information', async () => { - expect(anonymizedUser.profile).to.not.exist; - expect(anonymizedUser.contributor).to.not.exist; - expect(anonymizedUser.achievements.challenges).to.not.exist; - }); - - it('removes social information', async () => { - expect(anonymizedUser.newMessages).to.not.exist; - expect(anonymizedUser.invitations).to.not.exist; - expect(anonymizedUser.items.special.nyeReceived).to.not.exist; - expect(anonymizedUser.items.special.valentineReceived).to.not.exist; - - each(anonymizedUser.inbox.messages, (msg) => { - expect(msg.text).to.eql('inbox message text'); - }); - }); - - it('anonymizes task info', async () => { - each(['habits', 'todos', 'dailys', 'rewards'], (tasks) => { - each(anonymizedUser[tasks], (task) => { - expect(task.text).to.eql('task text'); - expect(task.notes).to.eql('task notes'); - - each(task.checklist, (box) => { - expect(box.text).to.match(/item\d*/); - }); - }); - }); - }); - - it('anonymizes tags', async () => { - each(anonymizedUser.tags, (tag) => { - expect(tag.name).to.eql('tag'); - expect(tag.challenge).to.eql('challenge'); - }); - }); - - it('removes webhooks', async () => { - expect(anonymizedUser.webhooks).to.not.exist; - }); -}); diff --git a/test/api/v2/user/batch-update/POST-user_batch-update.test.js b/test/api/v2/user/batch-update/POST-user_batch-update.test.js deleted file mode 100644 index f64cbc7f7b..0000000000 --- a/test/api/v2/user/batch-update/POST-user_batch-update.test.js +++ /dev/null @@ -1,62 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v2'; - -import { each } from 'lodash'; - -describe('POST /user/batch-update', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - context('allowed operations', () => { - it('makes batch operations', async () => { - let task = (await user.get('/user/tasks'))[0]; - - let updatedUser = await user.post('/user/batch-update', [ - {op: 'update', body: {'stats.hp': 30}}, - {op: 'update', body: {'profile.name': 'Samwise'}}, - {op: 'score', params: { direction: 'up', id: task.id }}, - ]); - - expect(updatedUser.stats.hp).to.eql(30); - expect(updatedUser.profile.name).to.eql('Samwise'); - - let fetchedTask = await user.get(`/user/tasks/${task.id}`); - - expect(fetchedTask.value).to.be.greaterThan(task.value); - }); - }); - - xcontext('development only operations', () => { // These tests will fail if your NODE_ENV is set to 'development' instead of 'testing' - let protectedOperations = { - 'Add Ten Gems': 'addTenGems', - 'Add Hourglass': 'addHourglass', - }; - - each(protectedOperations, (operation, description) => { - it(`it sends back a 500 error for ${description} operation`, async () => { - return expect(user.post('/user/batch-update', [ - { op: operation }, - ])).to.eventually.be.rejected.and.eql({ - code: 500, - text: t('messageUserOperationNotFound', { operation }), - }); - }); - }); - }); - - context('unknown operations', () => { - it('sends back a 500 error', async () => { - return expect(user.post('/user/batch-update', [ - {op: 'aNotRealOperation'}, - ])).to.eventually.be.rejected.and.eql({ - code: 500, - text: t('messageUserOperationNotFound', { operation: 'aNotRealOperation' }), - }); - }); - }); -}); diff --git a/test/api/v2/user/pushDevice/POST-pushDevice.test.js b/test/api/v2/user/pushDevice/POST-pushDevice.test.js deleted file mode 100644 index c0b5e9be72..0000000000 --- a/test/api/v2/user/pushDevice/POST-pushDevice.test.js +++ /dev/null @@ -1,24 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v2'; - -xdescribe('POST /user/pushDevice', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('registers a device id', async () => { - return user.post('/user/pushDevice', { - regId: '123123', - type: 'android', - }).then((devices) => { - let device = devices[0]; - - expect(device._id).to.exist; - expect(device.regId).to.eql('123123'); - expect(device.type).to.eql('android'); - }); - }); -}); diff --git a/test/api/v2/user/tasks/DELETE-tasks_id.test.js b/test/api/v2/user/tasks/DELETE-tasks_id.test.js deleted file mode 100644 index 68eb20faef..0000000000 --- a/test/api/v2/user/tasks/DELETE-tasks_id.test.js +++ /dev/null @@ -1,40 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v2'; - -describe('DELETE /user/tasks/:id', () => { - let user, task; - - beforeEach(async () => { - user = await generateUser(); - task = user.todos[0]; - }); - - it('deletes a task', async () => { - await user.del(`/user/tasks/${task.id}`); - - await expect(user.get(`/user/tasks/${task.id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - text: t('messageTaskNotFound'), - }); - }); - - it('returns an error if the task does not exist', async () => { - return expect(user.del('/user/tasks/task-that-does-not-exist')) - .to.eventually.be.rejected.and.eql({ - code: 404, - text: t('messageTaskNotFound'), - }); - }); - - it('does not delete another user\'s task', async () => { - return expect(generateUser().then((otherUser) => { - let otherUsersTask = otherUser.todos[0]; - return user.del(`/user/tasks/${otherUsersTask.id}`); - })).to.eventually.be.rejected.and.eql({ - code: 404, - text: 'Task not found.', - }); - }); -}); diff --git a/test/api/v2/user/tasks/GET-tasks.test.js b/test/api/v2/user/tasks/GET-tasks.test.js deleted file mode 100644 index 1506f4c2f0..0000000000 --- a/test/api/v2/user/tasks/GET-tasks.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v2'; - -describe('GET /user/tasks/', () => { - let user; - - beforeEach(async () => { - return generateUser().then((_user) => { - user = _user; - }); - }); - - it('gets all tasks', async () => { - return user.get('/user/tasks/').then((tasks) => { - expect(tasks).to.be.an('array'); - expect(tasks.length).to.equal(1); - - let task = tasks[0]; - expect(task.id).to.exist; - expect(task.type).to.exist; - expect(task.text).to.exist; - }); - }); -}); diff --git a/test/api/v2/user/tasks/GET-tasks_id.test.js b/test/api/v2/user/tasks/GET-tasks_id.test.js deleted file mode 100644 index cd9e1e73be..0000000000 --- a/test/api/v2/user/tasks/GET-tasks_id.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v2'; - -describe('GET /user/tasks/:id', () => { - let user, task; - - beforeEach(async () => { - user = await generateUser(); - task = user.todos[0]; - }); - - it('gets a task', async () => { - return user.get(`/user/tasks/${task.id}`).then((foundTask) => { - expect(foundTask.id).to.eql(task.id); - expect(foundTask.text).to.eql(task.text); - expect(foundTask.notes).to.eql(task.notes); - expect(foundTask.value).to.eql(task.value); - expect(foundTask.type).to.eql(task.type); - }); - }); - - it('returns an error if the task does not exist', async () => { - return expect(user.get('/user/tasks/task-that-does-not-exist')) - .to.eventually.be.rejected.and.eql({ - code: 404, - text: t('messageTaskNotFound'), - }); - }); - - it('does not get another user\'s task', async () => { - return expect(generateUser().then((otherUser) => { - let otherUsersTask = otherUser.todos[0]; - - return user.get(`/user/tasks/${otherUsersTask.id}`); - })).to.eventually.be.rejected.and.eql({ - code: 404, - text: t('messageTaskNotFound'), - }); - }); -}); diff --git a/test/api/v2/user/tasks/POST-clear-completed.test.js b/test/api/v2/user/tasks/POST-clear-completed.test.js deleted file mode 100644 index 40e6cbd5cf..0000000000 --- a/test/api/v2/user/tasks/POST-clear-completed.test.js +++ /dev/null @@ -1,26 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v2'; - -describe('POST /user/tasks/clear-completed', () => { - let user; - - beforeEach(async () => { - return generateUser().then((_user) => { - user = _user; - }); - }); - - it('removes all completed todos', async () => { - let toComplete = await user.post('/user/tasks', { - type: 'todo', - text: 'done', - }); - - await user.post(`/user/tasks/${toComplete._id}/up`); - - let todos = await user.get('/user/tasks?type=todo'); - let uncomplete = await user.post('/user/tasks/clear-completed'); - expect(todos.length).to.equal(uncomplete.length + 1); - }); -}); diff --git a/test/api/v2/user/tasks/POST-tasks.test.js b/test/api/v2/user/tasks/POST-tasks.test.js deleted file mode 100644 index 4fe4c9f5ea..0000000000 --- a/test/api/v2/user/tasks/POST-tasks.test.js +++ /dev/null @@ -1,66 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v2'; - -describe('POST /user/tasks', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('creates a task', async () => { - return user.post('/user/tasks').then((task) => { - expect(task.id).to.exist; - }); - }); - - it('creates a habit by default', async () => { - return expect(user.post('/user/tasks')) - .to.eventually.have.property('type', 'habit'); - }); - - it('creates a task with specified values', async () => { - return user.post('/user/tasks', { - type: 'daily', - text: 'My task', - notes: 'My notes', - frequency: 'daily', - }).then((task) => { - expect(task.type).to.eql('daily'); - expect(task.text).to.eql('My task'); - expect(task.notes).to.eql('My notes'); - expect(task.frequency).to.eql('daily'); - }); - }); - - xit('does not create a task with an id that already exists', async () => { - let todo = user.todos[0]; - - return expect(user.post('/user/tasks', { - id: todo.id, - })).to.eventually.be.rejected.and.eql({ - code: 409, - text: t('messageDuplicateTaskID'), - }); - }); - - xit('TODO: no error is thrown - throws a 500 validation error if invalid type is posted', async () => { - return expect(user.post('/user/tasks', { - type: 'not-valid', - })).to.eventually.be.rejected.and.eql({ - code: 500, - text: 'Cannot call method \'indexOf\' of undefined', - }); - }); - - xit('TODO: no error is thrown - throws a 500 validation error if invalid data is posted', async () => { - return expect(user.post('/user/tasks', { - frequency: 'not-valid', - })).to.eventually.be.rejected.and.eql({ - code: 500, - text: 'Task validation failed', - }); - }); -}); diff --git a/test/api/v2/user/tasks/PUT-tasks_id.test.js b/test/api/v2/user/tasks/PUT-tasks_id.test.js deleted file mode 100644 index ee41a4fe68..0000000000 --- a/test/api/v2/user/tasks/PUT-tasks_id.test.js +++ /dev/null @@ -1,64 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v2'; - -describe('PUT /user/tasks/:id', () => { - let user, task; - - beforeEach(async () => { - user = await generateUser(); - task = user.todos[0]; - }); - - it('does not update the id of the task', async () => { - return user.put(`/user/tasks/${task.id}`, { - id: 'some-thing', - }).then((updatedTask) => { - expect(updatedTask.id).to.eql(task.id); - expect(updatedTask.id).to.not.eql('some-thing'); - }); - }); - - it('does not update the type of the task', async () => { - return user.put(`/user/tasks/${task.id}`, { - type: 'habit', - }).then((updatedTask) => { - expect(updatedTask.type).to.eql(task.type); - expect(updatedTask.type).to.not.eql('habit'); - }); - }); - - it('updates text, attribute, priority and notes', async () => { - return user.put(`/user/tasks/${task.id}`, { - text: 'new text', - notes: 'new notes', - priority: 0.1, - attribute: 'str', - }).then((updatedTask) => { - expect(updatedTask.text).to.eql('new text'); - expect(updatedTask.notes).to.eql('new notes'); - expect(updatedTask.priority).to.eql(0.1); - expect(updatedTask.attribute).to.eql('str'); - }); - }); - - it('returns an error if the task does not exist', async () => { - return expect(user.put('/user/tasks/task-id-that-does-not-exist')) - .to.eventually.be.rejected.and.eql({ - code: 404, - text: 'Task not found.', - }); - }); - - it('does not update another user\'s task', async () => { - return expect(generateUser().then((otherUser) => { - let otherUsersTask = otherUser.todos[0]; - return user.put(`/user/tasks/${otherUsersTask._id}`, { - name: 'some name', - }); - })).to.eventually.be.rejected.and.eql({ - code: 404, - text: 'Task not found.', - }); - }); -}); diff --git a/test/api/v3/integration/emails/GET-email-unsubscribe.test.js b/test/api/v3/integration/emails/GET-email-unsubscribe.test.js index 1bd3a532fa..cda83e78e3 100644 --- a/test/api/v3/integration/emails/GET-email-unsubscribe.test.js +++ b/test/api/v3/integration/emails/GET-email-unsubscribe.test.js @@ -2,7 +2,7 @@ import { generateUser, translate as t, } from '../../../../helpers/api-v3-integration.helper'; -import { encrypt } from '../../../../../website/server/libs/api-v3/encryption'; +import { encrypt } from '../../../../../website/server/libs/encryption'; import { v4 as generateUUID } from 'uuid'; describe('GET /email/unsubscribe', () => { diff --git a/test/api/v3/integration/user/auth/POST-register_local.test.js b/test/api/v3/integration/user/auth/POST-register_local.test.js index 63d8f755a5..97d30a5f70 100644 --- a/test/api/v3/integration/user/auth/POST-register_local.test.js +++ b/test/api/v3/integration/user/auth/POST-register_local.test.js @@ -6,7 +6,7 @@ import { } from '../../../../../helpers/api-integration/v3'; import { v4 as generateRandomUserName } from 'uuid'; import { each } from 'lodash'; -import { encrypt } from '../../../../../../website/server/libs/api-v3/encryption'; +import { encrypt } from '../../../../../../website/server/libs/encryption'; describe('POST /user/auth/local/register', () => { context('username and email are free', () => { diff --git a/test/api/v3/unit/libs/analyticsService.test.js b/test/api/v3/unit/libs/analyticsService.test.js index 553dfb676c..4e9e491fca 100644 --- a/test/api/v3/unit/libs/analyticsService.test.js +++ b/test/api/v3/unit/libs/analyticsService.test.js @@ -1,7 +1,7 @@ // TODO These tests are pretty brittle // rewrite them to not depend on nock // Trust that the amplitude module works as intended and sends the requests -import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService'; +import analyticsService from '../../../../../website/server/libs/analyticsService'; import nock from 'nock'; diff --git a/test/api/v3/unit/libs/baseModel.test.js b/test/api/v3/unit/libs/baseModel.test.js index 39bf7df047..676366d42f 100644 --- a/test/api/v3/unit/libs/baseModel.test.js +++ b/test/api/v3/unit/libs/baseModel.test.js @@ -1,4 +1,4 @@ -import baseModel from '../../../../../website/server/libs/api-v3/baseModel'; +import baseModel from '../../../../../website/server/libs/baseModel'; import mongoose from 'mongoose'; describe('Base model plugin', () => { diff --git a/test/api/v3/unit/libs/buildManifest.test.js b/test/api/v3/unit/libs/buildManifest.test.js index c1213fd140..72cffec6c5 100644 --- a/test/api/v3/unit/libs/buildManifest.test.js +++ b/test/api/v3/unit/libs/buildManifest.test.js @@ -1,6 +1,6 @@ import { getManifestFiles, -} from '../../../../../website/server/libs/api-v3/buildManifest'; +} from '../../../../../website/server/libs/buildManifest'; describe('Build Manifest', () => { describe('getManifestFiles', () => { diff --git a/test/api/v3/unit/libs/collectionManipulators.test.js b/test/api/v3/unit/libs/collectionManipulators.test.js index da44fd5319..a2bc6e71c4 100644 --- a/test/api/v3/unit/libs/collectionManipulators.test.js +++ b/test/api/v3/unit/libs/collectionManipulators.test.js @@ -1,7 +1,7 @@ import mongoose from 'mongoose'; import { removeFromArray, -} from '../../../../../website/server/libs/api-v3/collectionManipulators'; +} from '../../../../../website/server/libs/collectionManipulators'; describe('Collection Manipulators', () => { describe('removeFromArray', () => { diff --git a/test/api/v3/unit/libs/cron.test.js b/test/api/v3/unit/libs/cron.test.js index 7141837f24..b6ed5b6c39 100644 --- a/test/api/v3/unit/libs/cron.test.js +++ b/test/api/v3/unit/libs/cron.test.js @@ -1,7 +1,7 @@ /* eslint-disable global-require */ import moment from 'moment'; import Bluebird from 'bluebird'; -import { recoverCron, cron } from '../../../../../website/server/libs/api-v3/cron'; +import { recoverCron, cron } from '../../../../../website/server/libs/cron'; import { model as User } from '../../../../../website/server/models/user'; import * as Tasks from '../../../../../website/server/models/task'; import { clone } from 'lodash'; diff --git a/test/api/v3/unit/libs/email.test.js b/test/api/v3/unit/libs/email.test.js index cfd18857ed..46476aa7ef 100644 --- a/test/api/v3/unit/libs/email.test.js +++ b/test/api/v3/unit/libs/email.test.js @@ -4,7 +4,7 @@ import nconf from 'nconf'; import nodemailer from 'nodemailer'; import Bluebird from 'bluebird'; import requireAgain from 'require-again'; -import logger from '../../../../../website/server/libs/api-v3/logger'; +import logger from '../../../../../website/server/libs/logger'; import { TAVERN_ID } from '../../../../../website/server/models/group'; function defer () { @@ -50,7 +50,7 @@ function getUser () { } describe('emails', () => { - let pathToEmailLib = '../../../../../website/server/libs/api-v3/email'; + let pathToEmailLib = '../../../../../website/server/libs/email'; describe('sendEmail', () => { it('can send an email using the default transport', () => { diff --git a/test/api/v3/unit/libs/encryption.test.js b/test/api/v3/unit/libs/encryption.test.js index a63a527e74..0396fded1c 100644 --- a/test/api/v3/unit/libs/encryption.test.js +++ b/test/api/v3/unit/libs/encryption.test.js @@ -1,7 +1,7 @@ import { encrypt, decrypt, -} from '../../../../../website/server/libs/api-v3/encryption'; +} from '../../../../../website/server/libs/encryption'; describe('encryption', () => { it('can encrypt and decrypt', () => { diff --git a/test/api/v3/unit/libs/errors.test.js b/test/api/v3/unit/libs/errors.test.js index efa694d5ab..d1c1c1e758 100644 --- a/test/api/v3/unit/libs/errors.test.js +++ b/test/api/v3/unit/libs/errors.test.js @@ -5,7 +5,7 @@ import { BadRequest, InternalServerError, NotFound, -} from '../../../../../website/server/libs/api-v3/errors'; +} from '../../../../../website/server/libs/errors'; describe('Custom Errors', () => { describe('CustomError', () => { diff --git a/test/api/v3/unit/libs/i18n.test.js b/test/api/v3/unit/libs/i18n.test.js index 06ebcbc0b6..9e31b1a406 100644 --- a/test/api/v3/unit/libs/i18n.test.js +++ b/test/api/v3/unit/libs/i18n.test.js @@ -2,7 +2,7 @@ import { translations, localePath, langCodes, -} from '../../../../../website/server/libs/api-v3/i18n'; +} from '../../../../../website/server/libs/i18n'; import fs from 'fs'; import path from 'path'; diff --git a/test/api/v3/unit/libs/logger.js b/test/api/v3/unit/libs/logger.js index b7e1d490fc..20189ccbe1 100644 --- a/test/api/v3/unit/libs/logger.js +++ b/test/api/v3/unit/libs/logger.js @@ -3,7 +3,7 @@ import requireAgain from 'require-again'; /* eslint-disable global-require */ describe('logger', () => { - let pathToLoggerLib = '../../../../../website/server/libs/api-v3/logger'; + let pathToLoggerLib = '../../../../../website/server/libs/logger'; let infoSpy; let errorSpy; diff --git a/test/api/v3/unit/libs/password.test.js b/test/api/v3/unit/libs/password.test.js index 68290aebc6..29de44676c 100644 --- a/test/api/v3/unit/libs/password.test.js +++ b/test/api/v3/unit/libs/password.test.js @@ -1,7 +1,7 @@ import { encrypt as encryptPassword, makeSalt, -} from '../../../../../website/server/libs/api-v3/password'; +} from '../../../../../website/server/libs/password'; describe('Password Utilities', () => { describe('Encrypt', () => { diff --git a/test/api/v3/unit/libs/payments.test.js b/test/api/v3/unit/libs/payments.test.js index 60394e83d4..33ba5d8f65 100644 --- a/test/api/v3/unit/libs/payments.test.js +++ b/test/api/v3/unit/libs/payments.test.js @@ -1,6 +1,6 @@ -import * as sender from '../../../../../website/server/libs/api-v3/email'; -import * as api from '../../../../../website/server/libs/api-v3/payments'; -import analytics from '../../../../../website/server/libs/api-v3/analyticsService'; +import * as sender from '../../../../../website/server/libs/email'; +import * as api from '../../../../../website/server/libs/payments'; +import analytics from '../../../../../website/server/libs/analyticsService'; import { model as User } from '../../../../../website/server/models/user'; import moment from 'moment'; diff --git a/test/api/v3/unit/libs/preening.test.js b/test/api/v3/unit/libs/preening.test.js index af503ca480..3c28557125 100644 --- a/test/api/v3/unit/libs/preening.test.js +++ b/test/api/v3/unit/libs/preening.test.js @@ -1,4 +1,4 @@ -import { preenHistory } from '../../../../../website/server/libs/api-v3/preening'; +import { preenHistory } from '../../../../../website/server/libs/preening'; import moment from 'moment'; import sinon from 'sinon'; // eslint-disable-line no-shadow import { generateHistory } from '../../../../helpers/api-unit.helper.js'; diff --git a/test/api/v3/unit/libs/pushNotifications.js b/test/api/v3/unit/libs/pushNotifications.js index cbc0d71bef..e891847891 100644 --- a/test/api/v3/unit/libs/pushNotifications.js +++ b/test/api/v3/unit/libs/pushNotifications.js @@ -6,7 +6,7 @@ import nconf from 'nconf'; describe('pushNotifications', () => { let user; let sendPushNotification; - let pathToPushNotifications = '../../../../../website/server/libs/api-v3/pushNotifications'; + let pathToPushNotifications = '../../../../../website/server/libs/pushNotifications'; let gcmSendSpy; let apnSendSpy; diff --git a/test/api/v3/unit/libs/setupNconf.test.js b/test/api/v3/unit/libs/setupNconf.test.js index 3e848b845f..afe000d969 100644 --- a/test/api/v3/unit/libs/setupNconf.test.js +++ b/test/api/v3/unit/libs/setupNconf.test.js @@ -1,4 +1,4 @@ -import setupNconf from '../../../../../website/server/libs/api-v3/setupNconf'; +import setupNconf from '../../../../../website/server/libs/setupNconf'; import path from 'path'; import nconf from 'nconf'; diff --git a/test/api/v3/unit/libs/webhooks.test.js b/test/api/v3/unit/libs/webhooks.test.js index 502bfe3839..85072792fb 100644 --- a/test/api/v3/unit/libs/webhooks.test.js +++ b/test/api/v3/unit/libs/webhooks.test.js @@ -1,5 +1,5 @@ import request from 'request'; -import { sendTaskWebhook } from '../../../../../website/server/libs/api-v3/webhook'; +import { sendTaskWebhook } from '../../../../../website/server/libs/webhook'; describe('webhooks', () => { beforeEach(() => { diff --git a/test/api/v3/unit/middlewares/analytics.test.js b/test/api/v3/unit/middlewares/analytics.test.js index 2a25380713..c55b212698 100644 --- a/test/api/v3/unit/middlewares/analytics.test.js +++ b/test/api/v3/unit/middlewares/analytics.test.js @@ -4,13 +4,13 @@ import { generateReq, generateNext, } from '../../../../helpers/api-unit.helper'; -import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService'; +import analyticsService from '../../../../../website/server/libs/analyticsService'; import nconf from 'nconf'; import requireAgain from 'require-again'; describe('analytics middleware', () => { let res, req, next; - let pathToAnalyticsMiddleware = '../../../../../website/server/middlewares/api-v3/analytics'; + let pathToAnalyticsMiddleware = '../../../../../website/server/middlewares/analytics'; beforeEach(() => { res = generateRes(); diff --git a/test/api/v3/unit/middlewares/cors.test.js b/test/api/v3/unit/middlewares/cors.test.js index 78d11651f8..051eada21e 100644 --- a/test/api/v3/unit/middlewares/cors.test.js +++ b/test/api/v3/unit/middlewares/cors.test.js @@ -4,7 +4,7 @@ import { generateReq, generateNext, } from '../../../../helpers/api-unit.helper'; -import cors from '../../../../../website/server/middlewares/api-v3/cors'; +import cors from '../../../../../website/server/middlewares/cors'; describe('cors middleware', () => { let res, req, next; diff --git a/test/api/v3/unit/middlewares/cronMiddleware.js b/test/api/v3/unit/middlewares/cronMiddleware.js index 4e58c20343..db3741b9cc 100644 --- a/test/api/v3/unit/middlewares/cronMiddleware.js +++ b/test/api/v3/unit/middlewares/cronMiddleware.js @@ -5,13 +5,13 @@ import { generateDaily, } from '../../../../helpers/api-unit.helper'; import { cloneDeep } from 'lodash'; -import cronMiddleware from '../../../../../website/server/middlewares/api-v3/cron'; +import cronMiddleware from '../../../../../website/server/middlewares/cron'; import moment from 'moment'; import { model as User } from '../../../../../website/server/models/user'; import { model as Group } from '../../../../../website/server/models/group'; import * as Tasks from '../../../../../website/server/models/task'; -import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService'; -import * as cronLib from '../../../../../website/server/libs/api-v3/cron'; +import analyticsService from '../../../../../website/server/libs/analyticsService'; +import * as cronLib from '../../../../../website/server/libs/cron'; import { v4 as generateUUID } from 'uuid'; describe('cron middleware', () => { diff --git a/test/api/v3/unit/middlewares/ensureAccessRight.test.js b/test/api/v3/unit/middlewares/ensureAccessRight.test.js index cc25e4f16b..fee84ce124 100644 --- a/test/api/v3/unit/middlewares/ensureAccessRight.test.js +++ b/test/api/v3/unit/middlewares/ensureAccessRight.test.js @@ -5,8 +5,8 @@ import { generateNext, } from '../../../../helpers/api-unit.helper'; import i18n from '../../../../../common/script/i18n'; -import { ensureAdmin, ensureSudo } from '../../../../../website/server/middlewares/api-v3/ensureAccessRight'; -import { NotAuthorized } from '../../../../../website/server/libs/api-v3/errors'; +import { ensureAdmin, ensureSudo } from '../../../../../website/server/middlewares/ensureAccessRight'; +import { NotAuthorized } from '../../../../../website/server/libs/errors'; describe('ensure access middlewares', () => { let res, req, next; diff --git a/test/api/v3/unit/middlewares/ensureDevelpmentMode.js b/test/api/v3/unit/middlewares/ensureDevelpmentMode.js index d7915b365f..0815bc0007 100644 --- a/test/api/v3/unit/middlewares/ensureDevelpmentMode.js +++ b/test/api/v3/unit/middlewares/ensureDevelpmentMode.js @@ -4,8 +4,8 @@ import { generateReq, generateNext, } from '../../../../helpers/api-unit.helper'; -import ensureDevelpmentMode from '../../../../../website/server/middlewares/api-v3/ensureDevelpmentMode'; -import { NotFound } from '../../../../../website/server/libs/api-v3/errors'; +import ensureDevelpmentMode from '../../../../../website/server/middlewares/ensureDevelpmentMode'; +import { NotFound } from '../../../../../website/server/libs/errors'; import nconf from 'nconf'; describe('developmentMode middleware', () => { diff --git a/test/api/v3/unit/middlewares/errorHandler.test.js b/test/api/v3/unit/middlewares/errorHandler.test.js index da7d54e667..1c973abe23 100644 --- a/test/api/v3/unit/middlewares/errorHandler.test.js +++ b/test/api/v3/unit/middlewares/errorHandler.test.js @@ -4,15 +4,15 @@ import { generateNext, } from '../../../../helpers/api-unit.helper'; -import errorHandler from '../../../../../website/server/middlewares/api-v3/errorHandler'; -import responseMiddleware from '../../../../../website/server/middlewares/api-v3/response'; +import errorHandler from '../../../../../website/server/middlewares/errorHandler'; +import responseMiddleware from '../../../../../website/server/middlewares/response'; import { getUserLanguage, attachTranslateFunction, -} from '../../../../../website/server/middlewares/api-v3/language'; +} from '../../../../../website/server/middlewares/language'; -import { BadRequest } from '../../../../../website/server/libs/api-v3/errors'; -import logger from '../../../../../website/server/libs/api-v3/logger'; +import { BadRequest } from '../../../../../website/server/libs/errors'; +import logger from '../../../../../website/server/libs/logger'; describe('errorHandler', () => { let res, req, next; diff --git a/test/api/v3/unit/middlewares/language.test.js b/test/api/v3/unit/middlewares/language.test.js index 23ef6deddd..20bac3c518 100644 --- a/test/api/v3/unit/middlewares/language.test.js +++ b/test/api/v3/unit/middlewares/language.test.js @@ -6,7 +6,7 @@ import { import { getUserLanguage, attachTranslateFunction, -} from '../../../../../website/server/middlewares/api-v3/language'; +} from '../../../../../website/server/middlewares/language'; import common from '../../../../../common'; import Bluebird from 'bluebird'; import { model as User } from '../../../../../website/server/models/user'; diff --git a/test/api/v3/unit/middlewares/maintenanceMode.test.js b/test/api/v3/unit/middlewares/maintenanceMode.test.js index 21cabe963d..857fb2c941 100644 --- a/test/api/v3/unit/middlewares/maintenanceMode.test.js +++ b/test/api/v3/unit/middlewares/maintenanceMode.test.js @@ -8,7 +8,7 @@ import requireAgain from 'require-again'; describe('maintenance mode middleware', () => { let res, req, next; - let pathToMaintenanceModeMiddleware = '../../../../../website/server/middlewares/api-v3/maintenanceMode'; + let pathToMaintenanceModeMiddleware = '../../../../../website/server/middlewares/maintenanceMode'; beforeEach(() => { res = generateRes(); diff --git a/test/api/v3/unit/middlewares/response.js b/test/api/v3/unit/middlewares/response.js index b3182ea87a..cbf2f178e3 100644 --- a/test/api/v3/unit/middlewares/response.js +++ b/test/api/v3/unit/middlewares/response.js @@ -3,7 +3,7 @@ import { generateReq, generateNext, } from '../../../../helpers/api-unit.helper'; -import responseMiddleware from '../../../../../website/server/middlewares/api-v3/response'; +import responseMiddleware from '../../../../../website/server/middlewares/response'; describe('response middleware', () => { let res, req, next; diff --git a/test/api/v3/unit/models/group.test.js b/test/api/v3/unit/models/group.test.js index b63e8ca06c..ff3785ef5a 100644 --- a/test/api/v3/unit/models/group.test.js +++ b/test/api/v3/unit/models/group.test.js @@ -2,7 +2,7 @@ import { sleep } from '../../../../helpers/api-unit.helper'; import { model as Group } from '../../../../../website/server/models/group'; import { model as User } from '../../../../../website/server/models/user'; import { quests as questScrolls } from '../../../../../common/script/content'; -import * as email from '../../../../../website/server/libs/api-v3/email'; +import * as email from '../../../../../website/server/libs/email'; import validator from 'validator'; import { TAVERN_ID } from '../../../../../common/script/'; diff --git a/test/api/v3/unit/models/task.test.js b/test/api/v3/unit/models/task.test.js index 7182b8e5a1..4ae02e6ca9 100644 --- a/test/api/v3/unit/models/task.test.js +++ b/test/api/v3/unit/models/task.test.js @@ -2,7 +2,7 @@ import { model as Challenge } from '../../../../../website/server/models/challen import { model as Group } from '../../../../../website/server/models/group'; import { model as User } from '../../../../../website/server/models/user'; import * as Tasks from '../../../../../website/server/models/task'; -import { InternalServerError } from '../../../../../website/server/libs/api-v3/errors'; +import { InternalServerError } from '../../../../../website/server/libs/errors'; import { each } from 'lodash'; import { generateHistory } from '../../../../helpers/api-unit.helper.js'; diff --git a/test/helpers/api-integration/translate.js b/test/helpers/api-integration/translate.js index 3ef7d68541..ceaf496b3c 100644 --- a/test/helpers/api-integration/translate.js +++ b/test/helpers/api-integration/translate.js @@ -1,5 +1,5 @@ import i18n from '../../../common/script/i18n'; -i18n.translations = require('../../../website/server/libs/api-v3/i18n').translations; +i18n.translations = require('../../../website/server/libs/i18n').translations; // Use this to verify error messages returned by the server // That way, if the translated string changes, the test diff --git a/test/helpers/api-integration/v2/index.js b/test/helpers/api-integration/v2/index.js deleted file mode 100644 index 8828b9b8ed..0000000000 --- a/test/helpers/api-integration/v2/index.js +++ /dev/null @@ -1,8 +0,0 @@ -// Import requester function, set it up for v2, export it -import { requester } from '../requester'; -requester.setApiVersion('v2'); -export { requester }; - -export { translate } from '../translate'; -export { checkExistence, resetHabiticaDB } from '../../mongo'; -export * from './object-generators'; diff --git a/test/helpers/api-integration/v2/object-generators.js b/test/helpers/api-integration/v2/object-generators.js deleted file mode 100644 index 3cd3c7d1d7..0000000000 --- a/test/helpers/api-integration/v2/object-generators.js +++ /dev/null @@ -1,146 +0,0 @@ -import { - times, - map, -} from 'lodash'; -import Bluebird from 'bluebird'; -import { v4 as generateUUID } from 'uuid'; -import { ApiUser, ApiGroup, ApiChallenge } from '../api-classes'; -import { requester } from '../requester'; - -// Creates a new user and returns it -// If you need the user to have specific requirements, -// such as a balance > 0, just pass in the adjustment -// to the update object. If you want to adjust a nested -// paramter, such as the number of wolf eggs the user has, -// , you can do so by passing in the full path as a string: -// { 'items.eggs.Wolf': 10 } -export async function generateUser (update = {}) { - let username = generateUUID(); - let password = 'password'; - let email = `${username}@example.com`; - - let user = await requester().post('/register', { - username, - email, - password, - confirmPassword: password, - }); - - let apiUser = new ApiUser(user); - - await apiUser.update(update); - - return apiUser; -} - -// Generates a new group. Requires a user object, which -// will will become the groups leader. Takes a details argument -// for the initial group creation and an update argument which -// will update the group via the db -export async function generateGroup (leader, details = {}, update = {}) { - details.type = details.type || 'party'; - details.privacy = details.privacy || 'private'; - details.name = details.name || 'test group'; - - let members; - - if (details.members) { - members = details.members; - delete details.members; - } - - let group = await leader.post('/groups', details); - let apiGroup = new ApiGroup(group); - - const groupMembershipTypes = { - party: { 'party._id': group._id}, - guild: { guilds: [group._id] }, - }; - - await Bluebird.all( - map(members, (member) => { - return member.update(groupMembershipTypes[group.type]); - }) - ); - - await apiGroup.update(update); - await apiGroup.sync(); - return apiGroup; -} - -// This is generate group + the ability to create -// real users to populate it. The settings object -// takes in: -// members: Number - the number of group members to create. Defaults to 0. -// inivtes: Number - the number of users to create and invite to the group. Defaults to 0. -// groupDetails: Object - how to initialize the group -// leaderDetails: Object - defaults for the leader, defaults with a gem balance so the user -// can create the group -// -// Returns an object with -// members: an array of user objects that correspond to the members of the group -// invitees: an array of user objects that correspond to the invitees of the group -// leader: the leader user object -// group: the group object -export async function createAndPopulateGroup (settings = {}) { - let numberOfMembers = settings.members || 0; - let numberOfInvites = settings.invites || 0; - let groupDetails = settings.groupDetails; - let leaderDetails = settings.leaderDetails || { balance: 10 }; - - let groupLeader = await generateUser(leaderDetails); - let group = await generateGroup(groupLeader, groupDetails); - - const groupMembershipTypes = { - party: { 'party._id': group._id}, - guild: { guilds: [group._id] }, - }; - - let members = await Bluebird.all( - times(numberOfMembers, () => { - return generateUser(groupMembershipTypes[group.type]); - }) - ); - - await group.update({ memberCount: numberOfMembers + 1}); - - let invitees = await Bluebird.all( - times(numberOfInvites, () => { - return generateUser(); - }) - ); - - let invitationPromises = invitees.map((invitee) => { - return groupLeader.post(`/groups/${group._id}/invite`, { - uuids: [invitee._id], - }); - }); - - await Bluebird.all(invitationPromises); - - return { - groupLeader, - group, - members, - invitees, - }; -} - -// Generates a new challenge. Requires an ApiGroup object with a -// _leader attribute (given with generateGroup method). The group -// will will become the group that owns the challenge. The group's -// leader will be the one to create the challenge. It takes a details -// argument for the initial challenge creation and an update argument -// which will update the challenge via the db -export async function generateChallenge (challengeCreator, group, details = {}, update = {}) { - details.group = group._id; - details.prize = details.prize || 0; - details.official = details.official || false; - - let challenge = await challengeCreator.post('/challenges', details); - let apiChallenge = new ApiChallenge(challenge); - - await apiChallenge.update(update); - - return apiChallenge; -} diff --git a/test/helpers/api-unit.helper.js b/test/helpers/api-unit.helper.js index 1c567928c6..42b717ae91 100644 --- a/test/helpers/api-unit.helper.js +++ b/test/helpers/api-unit.helper.js @@ -1,4 +1,4 @@ -import '../../website/server/libs/api-v3/i18n'; +import '../../website/server/libs/i18n'; import mongoose from 'mongoose'; import { defaultsDeep as defaults } from 'lodash'; import { model as User } from '../../website/server/models/user'; diff --git a/test/helpers/content.helper.js b/test/helpers/content.helper.js index c1ac3c5657..b97606c256 100644 --- a/test/helpers/content.helper.js +++ b/test/helpers/content.helper.js @@ -1,6 +1,6 @@ require('./globals.helper'); import i18n from '../../common/script/i18n'; -i18n.translations = require('../../website/server/libs/api-v3/i18n').translations; +i18n.translations = require('../../website/server/libs/i18n').translations; 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.$/; diff --git a/test/helpers/globals.helper.js b/test/helpers/globals.helper.js index 253d07e1d4..339a603021 100644 --- a/test/helpers/globals.helper.js +++ b/test/helpers/globals.helper.js @@ -23,12 +23,12 @@ import mongoose from 'mongoose'; // Load nconf for unit tests //------------------------------ if (process.env.LOAD_SERVER === '0') { // when the server is in a different process we simply connect to mongoose - require('../../website/server/libs/api-v3/setupNconf')('./config.json'); + require('../../website/server/libs/setupNconf')('./config.json'); // Use Q promises instead of mpromise in mongoose mongoose.Promise = Bluebird; mongoose.connect(nconf.get('TEST_DB_URI')); } else { // When running tests and the server in the same process - require('../../website/server/libs/api-v3/setupNconf')('./config.json.example'); + require('../../website/server/libs/setupNconf')('./config.json.example'); nconf.set('NODE_DB_URI', nconf.get('TEST_DB_URI')); nconf.set('NODE_ENV', 'test'); nconf.set('IS_TEST', true); diff --git a/website/server/controllers/api-v2/auth.js b/website/server/controllers/api-v2/auth.js deleted file mode 100644 index 9b27708614..0000000000 --- a/website/server/controllers/api-v2/auth.js +++ /dev/null @@ -1,362 +0,0 @@ -var _ = require('lodash'); -var validator = require('validator'); -var passport = require('passport'); -var shared = require('../../../../common'); -var async = require('async'); -var utils = require('../../libs/api-v2/utils'); -var nconf = require('nconf'); -var request = require('request'); -import { - model as User, -} from '../../models/user'; -import { - model as EmailUnsubscription, -} from '../../models/emailUnsubscription'; - -var analytics = utils.analytics; -var i18n = require('./../../libs/api-v2/i18n'); - -var isProd = nconf.get('NODE_ENV') === 'production'; - -var api = module.exports; - -var NO_TOKEN_OR_UID = { err: shared.i18n.t('messageAuthMustIncludeTokens') }; -var NO_USER_FOUND = {err: shared.i18n.t('messageAuthNoUserFound') }; -var NO_SESSION_FOUND = { err: shared.i18n.t('messageAuthMustBeLoggedIn') }; -var accountSuspended = function(uuid){ - return { - err: 'Account has been suspended, please contact leslie@habitica.com with your UUID ('+uuid+') for assistance.', - code: 'ACCOUNT_SUSPENDED' - }; -} - -api.auth = function(req, res, next) { - var uid = req.headers['x-api-user']; - var token = req.headers['x-api-key']; - if (!(uid && token)) return res.status(401).json(NO_TOKEN_OR_UID); - User.findOne({_id: uid, apiToken: token}, function(err, user) { - if (err) return next(err); - if (_.isEmpty(user)) return res.status(401).json(NO_USER_FOUND); - if (user.auth.blocked) return res.status(401).json(accountSuspended(user._id)); - - res.locals.wasModified = req.query._v ? +user._v !== +req.query._v : true; - res.locals.user = user; - req.session.userId = user._id; - return next(); - }); -}; - -api.authWithSession = function(req, res, next) { //[todo] there is probably a more elegant way of doing this... - if (!(req.session && req.session.userId)) - return res.status(401).json(NO_SESSION_FOUND); - User.findOne({_id: req.session.userId}, function(err, user) { - if (err) return next(err); - if (_.isEmpty(user)) return res.status(401).json(NO_USER_FOUND); - res.locals.user = user; - next(); - }); -}; - -// TODO passing auth params as query params is not safe as they are logged by browser history, ... -api.authWithUrl = function(req, res, next) { - User.findOne({_id:req.query._id, apiToken:req.query.apiToken}, function(err,user){ - if (err) return next(err); - if (_.isEmpty(user)) return res.status(401).json(NO_USER_FOUND); - res.locals.user = user; - next(); - }); -} - -api.registerUser = function(req, res, next) { - var email = req.body.email && req.body.email.toLowerCase(); - var username = req.body.username; - // Get the lowercase version of username to check that we do not have duplicates - // So we can search for it in the database and then reject the choosen username if 1 or more results are found - var lowerCaseUsername = username && username.toLowerCase(); - - async.auto({ - validate: function(cb) { - if (!(username && req.body.password && email)) - return cb({code:401, err: shared.i18n.t('messageAuthCredentialsRequired')}); - if (req.body.password !== req.body.confirmPassword) - return cb({code:401, err: shared.i18n.t('messageAuthPasswordMustMatch')}); - if (!validator.isEmail(email)) - return cb({code:401, err: ":email invalid"}); - cb(); - }, - findReg: function(cb) { - // Search for duplicates using lowercase version of username - User.findOne({$or:[{'auth.local.email': email}, {'auth.local.lowerCaseUsername': lowerCaseUsername}]}, {'auth.local':1}, cb); - }, - findFacebook: function(cb){ - User.findOne({_id: req.headers['x-api-user'], apiToken: req.headers['x-api-key']}, {auth:1}, cb); - }, - register: ['validate', 'findReg', 'findFacebook', function(cb, data) { - if (data.findReg) { - if (email === data.findReg.auth.local.email) return cb({code:401, err:"Email already taken"}); - // Check that the lowercase username isn't already used - if (lowerCaseUsername === data.findReg.auth.local.lowerCaseUsername) return cb({code:401, err: shared.i18n.t('messageAuthUsernameTaken')}); - } - var salt = utils.makeSalt(); - var newUser = { - auth: { - local: { - username: username, - lowerCaseUsername: lowerCaseUsername, // Store the lowercase version of the username - email: email, // Store email as lowercase - salt: salt, - hashed_password: utils.encryptPassword(req.body.password, salt) - }, - timestamps: {created: +new Date(), loggedIn: +new Date()} - } - }; - // existing user, allow them to add local authentication - if (data.findFacebook) { - data.findFacebook.auth.local = newUser.auth.local; - data.findFacebook.registeredThrough = newUser.registeredThrough; - data.findFacebook.save(cb); - // new user, register them - } else { - newUser.preferences = newUser.preferences || {}; - newUser.preferences.language = req.language; // User language detected from browser, not saved - var user = new User(newUser); - - user.registeredThrough = req.headers['x-client']; - var analyticsData = { - category: 'acquisition', - type: 'local', - gaLabel: 'local', - uuid: user._id, - }; - analytics.track('register', analyticsData) - - user.save(function(err, savedUser){ - if (err) return cb(err); - // Clean previous email preferences - EmailUnsubscription.remove({email: savedUser.auth.local.email}, function(){ - utils.txnEmail(savedUser, 'welcome'); - }); - cb.apply(cb, arguments); - }); - } - }] - }, function(err, data) { - if (err) return err.code ? res.status(err.code).json(err) : next(err); - data.register[0].getTransformedData(function(err, userTransformed){ - if(err) return next(err); - res.status(200).json(userTransformed); - }); - }); -}; - -api.loginLocal = function(req, res, next) { - var username = req.body.username; - var password = req.body.password; - if (!(username && password)) return res.status(401).json({err:'Missing :username or :password in request body, please provide both'}); - var login = validator.isEmail(username) ? - {'auth.local.email':username.toLowerCase()} : // Emails are all lowercase - {'auth.local.username':username}; // Use the username as the user typed it - - User.findOne(login, {auth:1}, function(err, user){ - if (err) return next(err); - if (!user) return res.status(401).json({err:"Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\" on the habitica.com website's login form."}); - if (user.auth.blocked) return res.status(401).json(accountSuspended(user._id)); - // We needed the whole user object first so we can get his salt to encrypt password comparison - User.findOne( - {$and: [login, {'auth.local.hashed_password': utils.encryptPassword(password, user.auth.local.salt)}]} - , {_id:1, apiToken:1} - , function(err, user){ - if (err) return next(err); - if (!user) return res.status(401).json({err:"Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\" on the habitica.com website's login form."}); - res.json({id: user._id,token: user.apiToken}); - password = null; - }); - }); -}; - -/* - POST /user/auth/social - */ -api.loginSocial = function(req, res, next) { - var access_token = req.body.authResponse.access_token, - network = req.body.network; - if (network!=='facebook') - return res.status(401).json({err:"Only Facebook supported currently."}); - async.auto({ - profile: function (cb) { - passport._strategies[network].userProfile(access_token, cb); - }, - user: ['profile', function (cb, results) { - var q = {}; - q['auth.' + network + '.id'] = results.profile.id; - User.findOne(q, {_id: 1, apiToken: 1, auth: 1}, cb); - }], - register: ['profile', 'user', function (cb, results) { - if (results.user) return cb(null, results.user); - // Create new user - var prof = results.profile; - var user = { - preferences: { - language: req.language // User language detected from browser, not saved - }, - auth: { - timestamps: {created: +new Date(), loggedIn: +new Date()} - } - }; - user.auth[network] = prof; - user = new User(user); - user.registeredThrough = req.headers['x-client']; - - user.save(function(err, savedUser){ - // Clean previous email preferences - if(savedUser.auth.facebook.emails && savedUser.auth.facebook.emails[0] && savedUser.auth.facebook.emails[0].value){ - EmailUnsubscription.remove({email: savedUser.auth.facebook.emails[0].value}, function(){ - utils.txnEmail(savedUser, 'welcome'); - }); - } - cb.apply(cb, arguments); - }); - - var analyticsData = { - category: 'acquisition', - type: network, - gaLabel: network, - uuid: user._id, - }; - analytics.track('register', analyticsData) - }] - }, function(err, results){ - if (err) return res.status(401).json({err: err.toString ? err.toString() : err}); - var acct = results.register[0] ? results.register[0] : results.register; - if (acct.auth.blocked) return res.status(401).json(accountSuspended(acct._id)); - return res.status(200).json({id:acct._id, token:acct.apiToken}); - }) -}; - -/** - * DELETE /user/auth/social - */ -api.deleteSocial = function(req,res,next){ - if (!res.locals.user.auth.local.username) - return res.status(401).json({err:"Account lacks another authentication method, can't detach Facebook"}); - //TODO for some reason, the following gives https://gist.github.com/lefnire/f93eb306069b9089d123 - //res.locals.user.auth.facebook = null; - //res.locals.user.auth.save(function(err, saved){ - User.update({_id:res.locals.user._id}, {$unset:{'auth.facebook':1}}, function(err){ - if (err) return next(err); - res.sendStatus(200); - }) -} - -api.resetPassword = function(req, res, next){ - var email = req.body.email && req.body.email.toLowerCase(), // Emails are all lowercase - salt = utils.makeSalt(), - newPassword = utils.makeSalt(), // use a salt as the new password too (they'll change it later) - hashed_password = utils.encryptPassword(newPassword, salt); - - if(!email) return res.status(400).json({err: "Email not provided"}); - - User.findOne({'auth.local.email': email}, function(err, user){ - if (err) return next(err); - if (!user) return res.status(401).json({err:"Sorry, we can't find a user registered with email " + email + "\n- Make sure your email address is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login."}); - user.auth.local.salt = salt; - user.auth.local.hashed_password = hashed_password; - utils.sendEmail({ - from: "Habitica ", - to: email, - subject: "Password Reset for Habitica", - text: "Password for " + user.auth.local.username + " has been reset to " + newPassword + " Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at " + nconf.get('BASE_URL') + ". After you've logged in, head to " + nconf.get('BASE_URL') + "/#/options/settings/settings and change your password.", - html: "Password for " + user.auth.local.username + " has been reset to " + newPassword + "

Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.

Log in at " + nconf.get('BASE_URL') + ". After you've logged in, head to " + nconf.get('BASE_URL') + "/#/options/settings/settings and change your password." - }); - user.save(function(err){ - if(err) return next(err); - res.send('New password sent to '+ email); - email = salt = newPassword = hashed_password = null; - }); - }); -}; - -var invalidPassword = function(user, password){ - var hashed_password = utils.encryptPassword(password, user.auth.local.salt); - if (hashed_password !== user.auth.local.hashed_password) - return {code:401, err:"Incorrect password"}; - return false; -} - -api.changeUsername = function(req, res, next) { - var user = res.locals.user; - var username = req.body.username; - var lowerCaseUsername = username && username.toLowerCase(); // we search for the lowercased version to intercept duplicates - - if(!username) return res.status(400).json({err: "Username not provided"}); - async.waterfall([ - function(cb){ - User.findOne({'auth.local.lowerCaseUsername': lowerCaseUsername}, {auth:1}, cb); - }, - function(found, cb){ - if (found) return cb({code:401, err: "Username already taken"}); - if (invalidPassword(user, req.body.password)) return cb(invalidPassword(user, req.body.password)); - user.auth.local.username = username; - user.auth.local.lowerCaseUsername = lowerCaseUsername; - - user.save(cb); - } - ], function(err){ - if (err) return err.code ? res.status(err.code).json(err) : next(err); - res.sendStatus(200); - }) -} - -api.changeEmail = function(req, res, next){ - var email = req.body.email && req.body.email.toLowerCase(); // emails are all lowercase - if(!email) return res.status(400).json({err: "Email not provided"}); - - async.waterfall([ - function(cb){ - User.findOne({'auth.local.email': email}, {auth:1}, cb); - }, - function(found, cb){ - if(found) return cb({code:401, err: shared.i18n.t('messageAuthEmailTaken')}); - if (invalidPassword(res.locals.user, req.body.password)) return cb(invalidPassword(res.locals.user, req.body.password)); - res.locals.user.auth.local.email = email; - res.locals.user.save(cb); - } - ], function(err){ - if (err) return err.code ? res.status(err.code).json(err) : next(err); - res.sendStatus(200); - }) -} - -api.changePassword = function(req, res, next) { - var user = res.locals.user, - oldPassword = req.body.oldPassword, - newPassword = req.body.newPassword, - confirmNewPassword = req.body.confirmNewPassword; - - if (newPassword != confirmNewPassword) - return res.status(401).json({err: "Password & Confirm don't match"}); - - var salt = user.auth.local.salt, - hashed_old_password = utils.encryptPassword(oldPassword, salt), - hashed_new_password = utils.encryptPassword(newPassword, salt); - - if (hashed_old_password !== user.auth.local.hashed_password) - return res.status(401).json({err:"Old password doesn't match"}); - - user.auth.local.hashed_password = hashed_new_password; - user.save(function(err, saved){ - if (err) next(err); - res.sendStatus(200); - }) -}; - -// DISABLED FOR API v2 -/*api.setupPassport = function(router) { - - router.get('/logout', i18n.getUserLanguage, function(req, res) { - req.logout(); - delete req.session.userId; - res.redirect('/'); - }) - -};*/ diff --git a/website/server/controllers/api-v2/challenges.js b/website/server/controllers/api-v2/challenges.js deleted file mode 100644 index c5d716d88e..0000000000 --- a/website/server/controllers/api-v2/challenges.js +++ /dev/null @@ -1,428 +0,0 @@ -// @see ../routes for routing - -var _ = require('lodash'); -var nconf = require('nconf'); -var async = require('async'); -var shared = require('../../../../common'); -import { - model as User, -} from '../../models/user'; -import { - model as Group, - basicFields as basicGroupFields, - TAVERN_ID, -} from '../../models/group'; -import { - model as Challenge, -} from '../../models/challenge'; -import * as Tasks from '../../models/task'; -var logging = require('./../../libs/api-v2/logging'); -var csvStringify = require('csv-stringify'); -var utils = require('../../libs/api-v2/utils'); -var api = module.exports; -var pushNotify = require('./pushNotifications'); -import Bluebird from 'bluebird'; -import v3MembersController from '../api-v3/members'; -/* - ------------------------------------------------------------------------ - Challenges - ------------------------------------------------------------------------ -*/ - -var nameFields = 'profile.name'; - -api.list = async function(req, res, next) { - try { - var user = res.locals.user; - - let challenges = await Challenge.find({ - $or: [ - {_id: {$in: user.challenges}}, // Challenges where the user is participating - {group: {$in: user.getGroups()}}, // Challenges in groups where I'm a member - {leader: user._id}, // Challenges where I'm the leader - ], - _id: {$ne: '95533e05-1ff9-4e46-970b-d77219f199e9'}, // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug TODO revisit - }) - .sort('-official -timestamp') - // .populate('group', basicGroupFields) - // .populate('leader', nameFields) - .exec(); - - let resChals = challenges.map(challenge => { - let obj = challenge.toJSON(); - - obj._isMember = user.challenges.indexOf(challenge._id) !== -1; - return obj; - }); - - // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 - await Bluebird.all(resChals.map((chal, index) => { - return Bluebird.all([ - User.findById(chal.leader).select(nameFields).exec(), - Group.findById(chal.group).select(basicGroupFields).exec(), - ]).then(populatedData => { - resChals[index].leader = populatedData[0] ? populatedData[0].toJSON({minimize: true}) : null; - resChals[index].group = populatedData[1] ? populatedData[1].toJSON({minimize: true}) : null; - }); - })); - - res.json(resChals); - } catch (err) { - next(err); - } -} - -// GET -api.get = async function(req, res, next) { - try { - let user = res.locals.user; - let challengeId = req.params.cid; - - let challenge = await Challenge.findById(challengeId) - // Don't populate the group as we'll fetch it manually later - // .populate('leader', nameFields) - .exec(); - if (!challenge) return res.status(404).json({err: 'Challenge ' + req.params.cid + ' not found'}); - - // Fetching basic group data - let group = await Group.getGroup({user, groupId: challenge.group, optionalMembership: true}); - if (!group || !challenge.canView(user, group)) return res.status(404).json({err: 'Challenge ' + req.params.cid + ' not found'}); - - let leaderRes = await User.findById(challenge.leader).select('profile.name').exec(); - leaderRes = leaderRes ? leaderRes.toJSON({minimize: true}) : null; - - challenge.getTransformedData({ - populateMembers: 'profile.name', - cb (err, transformedChal) { - transformedChal.group = group.toJSON({minimize: true}); - transformedChal.leader = leaderRes; - transformedChal._isMember = user.challenges.indexOf(transformedChal._id) !== -1; - res.json(transformedChal); - } - }); - } catch (err) { - next(err); - } -} - -api.csv = function(req, res, next) { - var cid = req.params.cid; - req.params.challengeId = cid; - v3MembersController.exportChallengeCsv.handler(req, res, next).catch(next); -} - -api.getMember = function(req, res, next) { - var cid = req.params.cid; - var uid = req.params.uid; - - req.params.memberId = uid; - req.params.challengeId = cid; - v3MembersController.getChallengeMemberProgress.handler(req, res, next) - .then(result => { - let newResult = { - profile: { - name: result.profile.name, - }, - habits: [], - dailys: [], - todos: [], - rewards: [], - }; - - let tasks = result.tasks; - tasks.forEach(task => { - let taskObj = task.toJSONV2(); - newResult[taskObj.type + 's'].push(taskObj); - }); - - res.json(newResult); - }) - .catch(next); -} - -// CREATE -api.create = async function(req, res, next){ - try { - var user = res.locals.user; - - let groupId = req.body.group; - let prize = req.body.prize; - - let group = await Group.getGroup({user, groupId, fields: '-chat', mustBeMember: true}); - if (!group) return res.status(404).json({err:"Group." + req.body.group + " not found"}); - if (!group.isMember(user)) return res.status(404).json({err:"Group." + req.body.group + " not found"}); - - if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id) { - return res.status(401).json({err:"Only the group leader can create challenges"}); - } - - if (group._id === TAVERN_ID && prize < 1) { - return res.status(401).json({err: 'Prize must be at least 1 Gem for public challenges.'}) - } - - if (prize > 0) { - let groupBalance = group.balance && group.leader === user._id ? group.balance : 0; - let prizeCost = prize / 4; - - if (prizeCost > user.balance + groupBalance) { - return res.status(401).json({err: 'You can\'t afford this prize. Purchase more gems or lower the prize amount.'}); - } - - if (groupBalance >= prizeCost) { - // Group pays for all of prize - group.balance -= prizeCost; - } else if (groupBalance > 0) { - // User pays remainder of prize cost after group - let remainder = prizeCost - group.balance; - group.balance = 0; - user.balance -= remainder; - } else { - // User pays for all of prize - user.balance -= prizeCost; - } - } - - group.challengeCount += 1; - - req.body.leader = user._id; - req.body.official = user.contributor.admin && req.body.official; - let challenge = new Challenge(Challenge.sanitize(req.body)); - - // First validate challenge so we don't save group if it's invalid (only runs sync validators) - let challengeValidationErrors = challenge.validateSync(); - if (challengeValidationErrors) throw challengeValidationErrors; - - req.body.habits = req.body.habits || []; - req.body.todos = req.body.todos || []; - req.body.dailys = req.body.dailys || []; - req.body.rewards = req.body.rewards || []; - - var chalTasks = req.body.habits.concat(req.body.rewards) - .concat(req.body.dailys).concat(req.body.todos) - .map(v2Task => Tasks.Task.fromJSONV2(v2Task)); - - chalTasks = chalTasks.map(function(task) { - var newTask = new Tasks[task.type](Tasks.Task.sanitize(task)); - newTask.challenge.id = challenge._id; - return newTask.save(); - }); - - let results = await Bluebird.all([challenge.save({ - validateBeforeSave: false, // already validated - }), group.save()].concat(chalTasks)); - let savedChal = results[0]; - - await savedChal.syncToUser(user); // (it also saves the user) - - savedChal.getTransformedData({ - cb (err, transformedChal) { - res.status(201).json(transformedChal); - }, - }); - } catch (err) { - next(err); - } -} - -// UPDATE -api.update = function(req, res, next){ - var cid = req.params.cid; - var user = res.locals.user; - var before; - var updatedTasks; - - async.waterfall([ - function(cb){ - // We first need the original challenge data, since we're going to compare against new & decide to sync users - Challenge.findById(cid, cb); - }, - function(chal, cb){ - if(!chal) return cb({chal: null}); - - chal.getTasks(function(err, tasks){ - cb(err, { - chal: chal, - tasks: tasks - }); - }); - }, - function(_before, cb) { - if (!_before.chal) return cb('Challenge ' + cid + ' not found'); - if (_before.chal.leader != user._id && !user.contributor.admin) return cb({code: 401, err: shared.i18n.t('noPermissionEditChallenge', req.language)}); - // Update the challenge, since syncing will need the updated challenge. But store `before` we're going to do some - // before-save / after-save comparison to determine if we need to sync to users - before = {chal: _before.chal, tasks: _before.tasks}; - var chalAttrs = _.pick(req.body, 'name shortName description date'.split(' ')); - async.parallel({ - chal: function(cb1){ - Challenge.findByIdAndUpdate(cid, {$set:chalAttrs}, {new: true}, cb1); - }, - tasks: function(cb1) { - // Convert to map of {id: task} so we can easily match them - var _beforeClonedTasks = _before.tasks; - updatedTasks = _.object(_.pluck(_beforeClonedTasks, '_id'), _beforeClonedTasks); - var newTasks = req.body.habits.concat(req.body.dailys) - .concat(req.body.todos).concat(req.body.rewards); - - var newTasksObj = _.object(_.pluck(newTasks, '_id'), newTasks); - async.forEachOf(newTasksObj, function(newTask, taskId, cb2){ - // some properties can't be changed - newTask = Tasks.Task.sanitize(newTask); - // we have to convert task to an object because otherwise things don't get merged correctly. Bad for performances? - _.assign(updatedTasks[taskId], shared.ops.updateTask(updatedTasks[taskId].toObject(), {body: newTask})); - _before.chal.updateTask(updatedTasks[taskId]).then(cb2).catch(cb2); - }, cb1); - } - }, cb); - }, - ], function(err, saved){ - if(err) { - return err.code ? res.json(err.code, err) : next(err); - } - - saved.chal.getTransformedData({cb: function(err, newChal){ - if(err) return next(err); - res.json(newChal); - }}) - cid = user = before = null; - }); -} - -/** - * Delete & close - */ -api.delete = async function(req, res, next){ - try { - var user = res.locals.user; - var cid = req.params.cid; - - let challenge = await Challenge.findOne({_id: req.params.cid}).exec(); - if (!challenge) return next('Challenge ' + cid + ' not found'); - if (!challenge.canModify(user)) return next(shared.i18n.t('noPermissionCloseChallenge')); - - // Close channel in background, some ops are run in the background without `await`ing - await challenge.closeChal({broken: 'CHALLENGE_DELETED'}); - res.sendStatus(200); - } catch (err) { - next(err); - } -} - -/** - * Select Winner & Close - */ -api.selectWinner = async function(req, res, next) { - try { - if (!req.query.uid) return res.status(401).json({err: 'Must select a winner'}); - - let challenge = await Challenge.findOne({_id: req.params.cid}).exec(); - if (!challenge) return next('Challenge ' + req.params.cid + ' not found'); - if (!challenge.canModify(res.locals.user)) return next(shared.i18n.t('noPermissionCloseChallenge')); - - let winner = await User.findOne({_id: req.query.uid}).exec(); - if (!winner || winner.challenges.indexOf(challenge._id) === -1) return next('Winner ' + req.query.uid + ' not found.'); - - // Close channel in background, some ops are run in the background without `await`ing - await challenge.closeChal({broken: 'CHALLENGE_CLOSED', winner}); - res.respond(200, {}); - } catch (err) { - next(err); - } -} - -api.join = async function(req, res, next){ - try { - var user = res.locals.user; - var cid = req.params.cid; - - let challenge = await Challenge.findOne({ _id: cid }); - if (!challenge) return next(shared.i18n.t('challengeNotFound')); - if (challenge.isMember(user)) return next(shared.i18n.t('userAlreadyInChallenge')); - - let group = await Group.getGroup({user, groupId: challenge.group, optionalMembership: true}); - if (!group || !challenge.hasAccess(user, group)) return next(shared.i18n.t('challengeNotFound')); - - challenge.memberCount += 1; - - // Add all challenge's tasks to user's tasks and save the challenge - await Bluebird.all([challenge.syncToUser(user), challenge.save()]); - - challenge.getTransformedData({ - cb (err, transformedChal) { - transformedChal._isMember = true; - res.json(transformedChal); - } - }); - } catch (e) { - next(e); - } -} - -api.leave = async function(req, res, next){ - try { - var user = res.locals.user; - var cid = req.params.cid; - // whether or not to keep challenge's tasks. strictly default to true if "keep-all" isn't provided - var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all'; - - let challenge = await Challenge.findOne({ _id: cid }); - if (!challenge) return next(shared.i18n.t('challengeNotFound')); - - let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy'}); - if (!group || !challenge.canView(user, group)) return next(shared.i18n.t('challengeNotFound')); - - if (!challenge.isMember(user)) return next(shared.i18n.t('challengeMemberNotFound')); - - challenge.memberCount -= 1; - - // Unlink challenge's tasks from user's tasks and save the challenge - await Bluebird.all([challenge.unlinkTasks(user, keep), challenge.save()]); - - challenge.getTransformedData({ - cb (err, transformedChal) { - transformedChal._isMember = false; - res.json(transformedChal); - } - }); - } catch (e) { - next(e); - } -} - -import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; - -api.unlink = async function(req, res, next) { - try { - var user = res.locals.user; - var tid = req.params.id; - var cid; - if (!req.query.keep) - return res.status(400).json({err: 'Provide unlink method as ?keep=keep-all (keep, keep-all, remove, remove-all)'}); - - let keep = req.query.keep; - let task = await Tasks.Task.findOne({ - _id: tid, - userId: user._id, - }).exec(); - - if (!task) return next(shared.i18n.t('taskNotFound')); - if (!task.challenge.id) return next(shared.i18n.t('cantOnlyUnlinkChalTask')); - - cid = task.challenge.id; - if (keep === 'keep') { - task.challenge = {}; - await task.save(); - } else { // remove - if (task.type !== 'todo' || !task.completed) { // eslint-disable-line no-lonely-if - removeFromArray(user.tasksOrder[`${task.type}s`], tid); - await Bluebird.all([user.save(), task.remove()]); - } else { - await task.remove(); - } - } - - res.sendStatus(200); - } catch (e) { - next(e); - } -} diff --git a/website/server/controllers/api-v2/coupon.js b/website/server/controllers/api-v2/coupon.js deleted file mode 100644 index 17cd289bd3..0000000000 --- a/website/server/controllers/api-v2/coupon.js +++ /dev/null @@ -1,47 +0,0 @@ -var _ = require('lodash'); -import { - model as Coupon, -} from '../../models/coupon'; -var api = module.exports; -var csvStringify = require('csv-stringify'); -var async = require('async'); - -api.ensureAdmin = function(req, res, next) { - if (!res.locals.user.contributor.sudo) return res.status(401).json({err:"You don't have admin access"}); - next(); -} - -api.generateCoupons = function(req,res,next) { - let count = Number(req.query.count); - Coupon.generate(req.params.event, count, function(err){ - if(err) return next(err); - res.sendStatus(200); - }); -} - -api.getCoupons = function(req,res,next) { - var options = {sort:'seq'}; - if (req.query.limit) options.limit = req.query.limit; - if (req.query.skip) options.skip = req.query.skip; - Coupon.find({},{}, options, function(err,coupons){ - let output = [['code']].concat(_.map(coupons, function(c){ - return [c._id]; - })) - - res.set({ - 'Content-Type': 'text/csv', - 'Content-disposition': 'attachment; filename=habitica-coupons.csv', - }); - csvStringify(output, (err, csv) => { - if (err) return next(err); - res.status(200).send(csv); - }); - }); -} - -api.enterCode = function(req,res,next) { - Coupon.apply(res.locals.user,req.params.code,function(err,user){ - if (err) return res.status(400).json({err:err}); - res.json(user); - }); -} diff --git a/website/server/controllers/api-v2/dataexport.js b/website/server/controllers/api-v2/dataexport.js deleted file mode 100644 index f0aad2b35c..0000000000 --- a/website/server/controllers/api-v2/dataexport.js +++ /dev/null @@ -1,153 +0,0 @@ -var _ = require('lodash'); -var express = require('express'); -var csvStringify = require('csv-stringify'); -var nconf = require('nconf'); -var moment = require('moment'); -var js2xmlparser = require("js2xmlparser"); -var pd = require('pretty-data').pd; -import { - model as User, -} from '../../models/user'; - -// Avatar screenshot/static-page includes -//var Pageres = require('pageres'); //https://github.com/sindresorhus/pageres -//var AWS = require('aws-sdk'); -//AWS.config.update({accessKeyId: nconf.get("S3:accessKeyId"), secretAccessKey: nconf.get("S3:secretAccessKey")}); -//var s3Stream = require('s3-upload-stream')(new AWS.S3()); //https://github.com/nathanpeck/s3-upload-stream -//var bucket = nconf.get("S3:bucket"); -//var request = require('request'); - -/* - ------------------------------------------------------------------------ - Data export - ------------------------------------------------------------------------ -*/ - -var dataexport = module.exports; - -dataexport.history = function(req, res) { - var user = res.locals.user; - var output = [ - ["Task Name", "Task ID", "Task Type", "Date", "Value"] - ]; - _.each(user.tasks, function(task) { - _.each(task.history, function(history) { - output.push([ - task.text, - task.id, - task.type, - moment(history.date).format("MM-DD-YYYY HH:mm:ss"), - history.value - ]); - }); - }); - - res.set({ - 'Content-Type': 'text/csv', - 'Content-disposition': 'attachment; filename=habitica-tasks-history.csv', - }); - - csvStringify(output, (err, csv) => { - if (err) return next(err); - res.status(200).send(csv); - }); -}; - -var userdata = function(user) { - if(user.auth && user.auth.local) { - delete user.auth.local.salt; - delete user.auth.local.hashed_password; - } - return user; -} - -dataexport.leanuser = function(req, res, next) { - User.findOne({_id: res.locals.user._id}).lean().exec(function(err, user) { - if (err) return res.status(500).json({err: err}); - if (_.isEmpty(user)) return res.status(401).json(NO_USER_FOUND); - res.locals.user = user; - return next(); - }); -}; - -dataexport.userdata = { - xml: function(req, res) { - var user = userdata(res.locals.user); - return res.xml({data: JSON.stringify(user), rootname: 'user'}); - }, - json: function(req, res) { - var user = userdata(res.locals.user); - return res.jsonstring(user); - } -} - -/* - ------------------------------------------------------------------------ - Express Extensions (should be refactored into a module) - ------------------------------------------------------------------------ -*/ - -var expressres = express.response || http.ServerResponse.prototype; - -expressres.xml = function(obj, headers, status) { - var body = ''; - this.charset = this.charset || 'utf-8'; - this.header('Content-Type', 'text/xml'); - this.header('Content-Disposition', 'attachment'); - body = pd.xml(js2xmlparser(obj.rootname,obj.data)); - return this.send(body, headers, status); -}; - -expressres.jsonstring = function(obj, headers, status) { - var body = ''; - this.charset = this.charset || 'utf-8'; - this.header('Content-Type', 'application/json'); - this.header('Content-Disposition', 'attachment'); - body = pd.json(JSON.stringify(obj)); - return this.send(body, headers, status); -}; - -/* - ------------------------------------------------------------------------ - Static page and image screenshot of avatar - ------------------------------------------------------------------------ - */ - - -dataexport.avatarPage = function(req, res) { - User.findById(req.params.uuid).select('stats profile items achievements preferences backer contributor').exec(function(err, user){ - res.render('avatar-static', { - title: user.profile.name, - env: _.defaults({user:user}, res.locals.habitrpg) - }); - }) -}; - -dataexport.avatarImage = function(req, res, next) { - var filename = 'avatars/'+req.params.uuid+'.png'; - request.head('https://'+bucket+'.s3.amazonaws.com/'+filename, function(err,response,body) { - // cache images for 10 minutes on aws, else upload a new one - if (response.statusCode==200 && moment().diff(response.headers['last-modified'], 'minutes') < 10) - return res.redirect(301, 'https://' + bucket + '.s3.amazonaws.com/' + filename); - new Pageres()//{delay:1} - .src(nconf.get('BASE_URL') + '/export/avatar-' + req.params.uuid + '.html', ['140x147'], {crop: true, filename: filename.replace('.png', '')}) - .run() - .then(function (file) { - var upload = s3Stream.upload({ - Bucket: bucket, - Key: filename, - ACL: "public-read", - StorageClass: "REDUCED_REDUNDANCY", - ContentType: "image/png", - Expires: +moment().add({minutes: 3}) - }); - upload.on('error', function (err) { - next(err); - }); - upload.on('uploaded', function (details) { - res.redirect(details.Location); - }); - file[0].pipe(upload); - }).catch(next); - }) -}; diff --git a/website/server/controllers/api-v2/groups.js b/website/server/controllers/api-v2/groups.js deleted file mode 100644 index 961faabb0f..0000000000 --- a/website/server/controllers/api-v2/groups.js +++ /dev/null @@ -1,1222 +0,0 @@ -'use strict'; -// @see ../routes for routing - -function clone(a) { - return JSON.parse(JSON.stringify(a)); -} - -var _ = require('lodash'); -var nconf = require('nconf'); -var async = require('async'); -var Bluebird = require('bluebird'); -var utils = require('./../../libs/api-v2/utils'); -var shared = require('../../../../common'); - -import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; - -import { - model as User, -} from './../../models/user'; -import { - model as Group, - TAVERN_ID, -} from './../../models/group'; -import { - model as Challenge, -} from './../../models/challenge'; -import { - model as EmailUnsubscription, -} from './../../models/emailUnsubscription'; - -var isProd = nconf.get('NODE_ENV') === 'production'; -var api = module.exports; -var pushNotify = require('./pushNotifications'); -var analytics = utils.analytics; - -/* - ------------------------------------------------------------------------ - Groups - ------------------------------------------------------------------------ -*/ - -var partyFields = api.partyFields = 'profile preferences stats achievements party backer contributor auth.timestamps items'; -var nameFields = 'profile.name'; -var challengeFields = '_id name'; -var guildPopulate = {path: 'members', select: nameFields, options: {limit: 15} }; -const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map(email => {return {email, canSend: true}}); - -/** - * For parties, we want a lot of member details so we can show their avatars in the header. For guilds, we want very - * limited fields - and only a sampling of the members, beacuse they can be in the thousands - * @param type: 'party' or otherwise - * @param q: the Mongoose query we're building up - * @param additionalFields: if we want to populate some additional field not fetched normally - * pass it as a string, parties only - */ -var populateQuery = function(type, q, additionalFields){ - if (type == 'party') - q.populate('members', partyFields + (additionalFields ? (' ' + additionalFields) : '')); - else - q.populate(guildPopulate); - q.populate('leader', nameFields); - q.populate('invites', nameFields); - q.populate({ - path: 'challenges', - match: (type=='habitrpg') ? {_id:{$ne:'95533e05-1ff9-4e46-970b-d77219f199e9'}} : undefined, // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug - select: challengeFields, - options: {sort: {official: -1, timestamp: -1}} - }); - return q; -} - -/** - * Fetch groups list. This no longer returns party or tavern, as those can be requested indivdually - * as /groups/party or /groups/tavern - */ -api.list = function(req, res, next) { - var user = res.locals.user; - var groupFields = 'name description memberCount balance leader'; - var sort = '-memberCount'; - var type = req.query.type || 'party,guilds,public,tavern'; - - async.parallel({ - - // unecessary given our ui-router setup - party: function(cb){ - if (!~type.indexOf('party')) return cb(null, {}); - Group.findOne({_id: user.party._id, type: 'party'}) - .select(groupFields).exec(function(err, party){ - if (err) return cb(err); - if (!party) return cb(null, []); - party.getTransformedData({cb: function (err, transformedParty) { - if (err) return cb(err); - cb(null, (transformedParty === null ? [] : [transformedParty])); // return as an array for consistent ngResource use - }}); - }); - }, - - guilds: function(cb) { - if (!~type.indexOf('guilds')) return cb(null, []); - Group.find({_id: {'$in': user.guilds}, type:'guild'}) - .select(groupFields).sort(sort).exec(function (err, guilds) { - if (err) return cb(err); - async.map(guilds, function (guild, cb1) { - guild.getTransformedData({cb: cb1}) - }, function(err, guildsTransormed) { - cb(err, guildsTransormed); - }); - }); - }, - - 'public': function(cb) { - if (!~type.indexOf('public')) return cb(null, []); - Group.find({privacy: 'public'}) - .select(groupFields) - .sort(sort) - .lean() - .exec(function(err, groups){ - if (err) return cb(err); - _.each(groups, function(g){ - // To save some client-side performance, don't send down the full members arr, just send down temp var _isMember - if (user.guilds.indexOf(g._id) !== -1) g._isMember = true; - }); - cb(null, groups); - }); - }, - - // unecessary given our ui-router setup - tavern: function(cb) { - if (!~type.indexOf('tavern')) return cb(null, {}); - Group.findById(TAVERN_ID).select(groupFields).exec(function(err, tavern){ - if (err) return cb(err); - tavern.getTransformedData({cb: function (err, transformedTavern) { - if (err) return cb(err); - cb(null, ([transformedTavern])); // return as an array for consistent ngResource use - }}); - }); - } - - }, function(err, results){ - if (err) return next(err); - // ngResource expects everything as arrays. We used to send it down as a structured object: {public:[], party:{}, guilds:[], tavern:{}} - // but unfortunately ngResource top-level attrs are considered the ngModels in the list, so we had to do weird stuff and multiple - // requests to get it to work properly. Instead, we're not depending on the client to do filtering / organization, and we're - // just sending down a merged array. Revisit - var arr = _.reduce(results, function(m,v){ - if (_.isEmpty(v)) return m; - return m.concat(_.isArray(v) ? v : [v]); - }, []) - res.json(arr); - - user = groupFields = sort = type = null; - }) -}; - -/** - * Get group - * TODO: implement requesting fields ?fields=chat,members - */ -api.get = function(req, res, next) { - var user = res.locals.user; - var gid = req.params.gid; - let isUserGuild = user.guilds.indexOf(gid) !== -1; - - var q; - - if (gid === 'party' || gid === user.party._id) { - q = Group.findOne({_id: user.party._id, type: 'party'}) - } else { - - if (isUserGuild) { - q = Group.findOne({type: 'guild', _id: gid}); - } else if (gid === 'habitrpg') { - q = Group.findOne({_id: TAVERN_ID}); - } else { - q = Group.findOne({type: 'guild', privacy: 'public', _id: gid}); - } - } - - q.populate('leader', nameFields); - - //populateQuery(gid, q); - q.exec(function(err, group){ - if (err) return next(err); - if(!group){ - if(gid !== 'party') return res.status(404).json({err: shared.i18n.t('messageGroupNotFound')}); - - // Don't send a 404 when querying for a party even if it doesn't exist - // so that users with no party don't get a 404 on every access to the site - return res.json(group); - } - - group.getTransformedData({ - cb: function (err, transformedGroup) { - if (err) return next(err); - - if (!user.contributor.admin) { - _purgeFlagInfoFromChat(transformedGroup, user); - } - - //Since we have a limit on how many members are populate to the group, we want to make sure the user is always in the group - var userInGroup = _.find(transformedGroup.members, function(member){ return member._id == user._id; }); - if ((gid === 'party' || isUserGuild) && !userInGroup) { - transformedGroup.members.splice(0,1); - transformedGroup.members.push(user); - } - - res.json(transformedGroup); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - populateInvites: nameFields, - populateChallenges: challengeFields, - }); - }); -}; - - -api.create = function(req, res, next) { - var group = new Group(req.body); - var user = res.locals.user; - //group.members = [user._id]; - group.leader = user._id; - if (!group.name) group.name = 'group name'; - - if(group.type === 'guild'){ - user.guilds.push(group._id); - if(user.balance < 1) return res.status(401).json({err: shared.i18n.t('messageInsufficientGems')}); - - group.balance = 1; - user.balance--; - - async.waterfall([ - function(cb){user.save(cb)}, - function(saved,ct,cb){group.save(cb)}, - function(saved,ct,cb){ - saved.getTransformedData({ - populateMembers: nameFields, - cb, - }) - } - ],function(err,groupTransformed){ - if (err) return next(err); - res.json(groupTransformed); - group = user = null; - }); - - } else{ - if (user.party._id) return res.status(400).json({err:shared.i18n.t('messageGroupAlreadyInParty')}); - user.party._id = group._id; - user.save(function (err) { - if (err) return next(err); - group.save(function(err, saved) { - if (err) return next(err); - saved.getTransformedData({ - populateMembers: nameFields, - cb (err, groupTransformed) { - res.json(groupTransformed); - }, - }); - }); - }) - } -} - -api.update = function(req, res, next) { - var group = res.locals.group; - var user = res.locals.user; - - if(group.leader !== user._id) - return res.status(401).json({err: shared.i18n.t('messageGroupOnlyLeaderCanUpdate')}); - - 'name description logo logo leaderMessage leader leaderOnly'.split(' ').forEach(function(attr){ - if (req.body[attr]) group[attr] = req.body[attr]; - }); - - group.save(function(err, saved){ - if (err) return next(err); - - res.sendStatus(204); - }); -} - -// TODO remove from api object? -api.attachGroup = function(req, res, next) { - var user = res.locals.user; - var gid = req.params.gid === 'party' ? user.party._id : req.params.gid; - if (gid === 'habitrpg') gid = TAVERN_ID; - - let q = Group.findOne({_id: gid}) - - q.exec(function(err, group){ - if(err) return next(err); - if(!group) return res.status(404).json({err: shared.i18n.t('messageGroupNotFound')}); - - if (!user.contributor.admin) { - _purgeFlagInfoFromChat(group, user); - } - - res.locals.group = group; - next(); - }); -} - -api.getChat = function(req, res, next) { - // TODO: This code is duplicated from api.get - pull it out into a function to remove duplication. - var user = res.locals.user; - var gid = req.params.gid; - - var q; - let isUserGuild = user.guilds.indexOf(gid) !== -1; - - if (gid === 'party' || gid === user.party._id) { - q = Group.findOne({_id: user.party._id, type: 'party'}) - } else { - if (isUserGuild) { - q = Group.findOne({type: 'guild', _id: gid}); - } else if (gid === 'habitrpg') { - q = Group.findOne({_id: TAVERN_ID}); - } else { - q = Group.findOne({type: 'guild', privacy: 'public', _id: gid}); - } - } - - q.exec(function(err, group){ - if (err) return next(err); - if (!group && gid!=='party') return res.status(404).json({err: shared.i18n.t('messageGroupNotFound')}); - - res.json(res.locals.group.chat); - gid = null; - }); -}; - -/** - * TODO make this it's own ngResource so we don't have to send down group data with each chat post - */ -api.postChat = function(req, res, next) { - if(!req.query.message) { - return res.status(400).json({err: shared.i18n.t('messageGroupChatBlankMessage')}); - } else { - var user = res.locals.user - var group = res.locals.group; - if (group.type!='party' && user.flags.chatRevoked) return res.status(401).json({err:'Your chat privileges have been revoked.'}); - var lastClientMsg = req.query.previousMsg; - var chatUpdated = (lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg) ? true : false; - - group.sendChat(req.query.message, user); // TODO this should be body, but ngResource is funky - - if (group.type === 'party') { - user.party.lastMessageSeen = group.chat[0].id; - user.save(); - } - - group.save(function(err, saved){ - if (err) return next(err); - chatUpdated ? res.json({chat: group.chat}) : res.json({message: saved.chat[0]}); - group = chatUpdated = null; - }); - } -} - -api.deleteChatMessage = function(req, res, next){ - var user = res.locals.user - var group = res.locals.group; - var message = _.find(group.chat, {id: req.params.messageId}); - - if(!message) return res.status(404).json({err: "Message not found!"}); - - if(user._id !== message.uuid && !(user.backer && user.contributor.admin)) - return res.status(401).json({err: "Not authorized to delete this message!"}) - - var lastClientMsg = req.query.previousMsg; - var chatUpdated = (lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg) ? true : false; - - Group.update({_id:group._id}, {$pull:{chat:{id: req.params.messageId}}}, function(err){ - if(err) return next(err); - chatUpdated ? res.json({chat: group.chat}) : res.sendStatus(204); - group = chatUpdated = null; - }); -} - -api.flagChatMessage = function(req, res, next){ - var user = res.locals.user - var group = res.locals.group; - var message = _.find(group.chat, {id: req.params.mid}); - - if(!message) return res.status(404).json({err: shared.i18n.t('messageGroupChatNotFound')}); - if(message.uuid == user._id) return res.status(401).json({err: shared.i18n.t('messageGroupChatFlagOwnMessage')}); - - User.findOne({_id: message.uuid}, {auth: 1}, function(err, author){ - if(err) return next(err); - - // Log user ids that have flagged the message - if(!message.flags) message.flags = {}; - if(message.flags[user._id] && !user.contributor.admin) return res.status(401).json({err: shared.i18n.t('messageGroupChatFlagAlreadyReported')}); - message.flags[user._id] = true; - - // Log total number of flags (publicly viewable) - if(!message.flagCount) message.flagCount = 0; - if(user.contributor.admin){ - // Arbitraty amount, higher than 2 - message.flagCount = 5; - } else { - message.flagCount++ - } - - Group.update({_id: group._id, 'chat.id': message.id}, {'$set': { - 'chat.$.flags': message.flags, - 'chat.$.flagCount': message.flagCount, - }}, function(err) { - if (err) return next(err); - - utils.txnEmail(FLAG_REPORT_EMAILS, 'flag-report-to-mods', [ - {name: "MESSAGE_TIME", content: (new Date(message.timestamp)).toString()}, - {name: "MESSAGE_TEXT", content: message.text}, - - {name: "REPORTER_USERNAME", content: user.profile.name}, - {name: "REPORTER_UUID", content: user._id}, - {name: "REPORTER_EMAIL", content: user.auth.local ? user.auth.local.email : ((user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0]) ? user.auth.facebook.emails[0].value : null)}, - {name: "REPORTER_MODAL_URL", content: "/static/front/#?memberId=" + user._id}, - - {name: "AUTHOR_USERNAME", content: message.user}, - {name: "AUTHOR_UUID", content: message.uuid}, - {name: "AUTHOR_EMAIL", content: author.auth.local ? author.auth.local.email : ((author.auth.facebook && author.auth.facebook.emails && author.auth.facebook.emails[0]) ? author.auth.facebook.emails[0].value : null)}, - {name: "AUTHOR_MODAL_URL", content: "/static/front/#?memberId=" + message.uuid}, - - {name: "GROUP_NAME", content: group.name}, - {name: "GROUP_TYPE", content: group.type}, - {name: "GROUP_ID", content: group._id}, - {name: "GROUP_URL", content: group._id == TAVERN_ID ? '/#/options/groups/tavern' : (group.type === 'guild' ? ('/#/options/groups/guilds/' + group._id) : 'party')}, - ]); - - return res.sendStatus(204); - }); - }); -} - -api.clearFlagCount = function(req, res, next){ - var user = res.locals.user - var group = res.locals.group; - var message = _.find(group.chat, {id: req.params.mid}); - - if(!message) return res.status(404).json({err: shared.i18n.t('messageGroupChatNotFound')}); - - if(user.contributor.admin){ - message.flagCount = 0; - - Group.update({_id: group._id, 'chat.id': message.id}, {'$set': { - 'chat.$.flagCount': message.flagCount, - }}, function(err) { - if(err) return next(err); - return res.sendStatus(204); - }); - } else { - return res.status(401).json({err: shared.i18n.t('messageGroupChatAdminClearFlagCount')}) - } -} - -api.seenMessage = function(req,res,next){ - // Skip the auth step, we want this to be fast. If !found with uuid/token, then it just doesn't save - // Check for req.params.gid to exist - if(req.params.gid){ - var update = {$unset:{}}; - update['$unset']['newMessages.'+req.params.gid] = ''; - User.update({_id:req.headers['x-api-user'], apiToken:req.headers['x-api-key']},update).exec(); - } - res.sendStatus(200); -} - -api.likeChatMessage = function(req, res, next) { - var user = res.locals.user; - var group = res.locals.group; - var message = _.find(group.chat, {id: req.params.mid}); - - if (!message) return res.status(404).json({err: shared.i18n.t('messageGroupChatNotFound')}); - if (message.uuid == user._id) return res.status(401).json({err: shared.i18n.t('messageGroupChatLikeOwnMessage')}); - if (!message.likes) message.likes = {}; - if (message.likes[user._id]) { - delete message.likes[user._id]; - } else { - message.likes[user._id] = true; - } - - Group.update({_id: group._id, 'chat.id': message.id}, {'$set': { - 'chat.$.likes': message.likes - }}, function(err) { - if (err) return next(err); - return res.send(group.chat); - }); -} - -api.join = function(req, res, next) { - var user = res.locals.user, - group = res.locals.group, - isUserInvited = false; - - if (group.type == 'party' && group._id == (user.invitations && user.invitations.party && user.invitations.party.id)) { - User.update({_id:user.invitations.party.inviter}, {$inc:{'items.quests.basilist':1}}).exec(); // Reward inviter - user.invitations.party = {}; // Clear invite - user.markModified('invitations.party'); - user.party._id = group._id; - user.save(); - // invite new user to pending quest - if (group.quest.key && !group.quest.active) { - User.update({_id:user._id},{$set: {'party.quest.RSVPNeeded': true, 'party.quest.key': group.quest.key}}).exec(); - group.quest.members[user._id] = undefined; - group.markModified('quest.members'); - } - isUserInvited = true; - } else if (group.type == 'guild') { - var i = _.findIndex(user.invitations.guilds, {id:group._id}); - if (~i){ - isUserInvited = true; - user.invitations.guilds.splice(i,1); - user.guilds.push(group._id); - user.save(); - }else{ - isUserInvited = group.privacy === 'private' ? false : true; - if (isUserInvited) { - user.guilds.push(group._id); - user.save(); - } - } - } - - if(!isUserInvited) return res.status(401).json({err: shared.i18n.t('messageGroupRequiresInvite')}); - - if (group.memberCount === 0) { - group.leader = user._id; - } - - /*if (!_.contains(group.members, user._id)){ - if (group.members.length === 0) { - group.leader = user._id; - } - - group.members.push(user._id); - - if (group.invites.length > 0) { - group.invites.splice(_.indexOf(group.invites, user._id), 1); - } - }*/ - - async.series([ - function(cb){ - group.save(cb); - }, - function(cb){ - group.getTransformedData({ - cb, - populateMembers: group.type === 'party' ? partyFields : nameFields, - populateInvites: nameFields, - populateChallenges: challengeFields, - }) - } - ], function(err, results){ - if (err) return next(err); - // Return the group? Or not? - res.json(results[1]); - group = null; - }); -} - -api.leave = function(req, res, next) { - var user = res.locals.user; - var group = res.locals.group; - - if (group.type === 'party') { - if (group.quest && group.quest.leader === user._id) { - return res.json(403, 'You cannot leave your party when you have started a quest. Abort the quest first.'); - } - - if (group.quest && group.quest.active && group.quest.members && group.quest.members[user._id]) { - return res.json(403, 'You cannot leave party during an active quest. Please leave the quest first.'); - } - } - - // When removing the user from challenges, should we keep the tasks? - var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all'; - - group.leave(user, keep) - .then(() => res.sendStatus(204)) - .catch(next); -}; - -var inviteByUUIDs = function(uuids, group, req, res, next){ - async.each(uuids, function(uuid, cb){ - User.findById(uuid, function(err,invite){ - if (err) return cb(err); - if (!invite) - return cb({code:400,err:'User with id "' + uuid + '" not found'}); - if (group.type == 'guild') { - if (_.contains(invite.guilds, group._id)) - return cb({code:400, err: "User already in that group"}); - if (invite.invitations && invite.invitations.guilds && _.find(invite.invitations.guilds, {id:group._id})) - return cb({code:400, err:"User already invited to that group"}); - sendInvite(); - } else if (group.type == 'party') { - if (invite.invitations && !_.isEmpty(invite.invitations.party)) - return cb({code: 400,err:"User already pending invitation."}); - if (invite.party && invite.party._id) { - return cb({code: 400, err: "User already in a party."}) - } - sendInvite(); - } - - function sendInvite (){ - if(group.type === 'guild'){ - invite.invitations.guilds.push({id: group._id, name: group.name, inviter:res.locals.user._id}); - - pushNotify.sendNotify(invite, shared.i18n.t('invitedGuild'), group.name); - }else{ - //req.body.type in 'guild', 'party' - invite.invitations.party = {id: group._id, name: group.name, inviter:res.locals.user._id}; - - pushNotify.sendNotify(invite, shared.i18n.t('invitedParty'), group.name); - } - - //group.invites.push(invite._id); - - async.series([ - function(cb){ - invite.save(cb); - } - ], function(err, results){ - if (err) return cb(err); - - if(invite.preferences.emailNotifications['invited' + (group.type == 'guild' ? 'Guild' : 'Party')] !== false){ - var inviterVars = utils.getUserInfo(res.locals.user, ['name', 'email']); - var emailVars = [ - {name: 'INVITER', content: inviterVars.name} - ]; - - if(group.type == 'guild'){ - emailVars.push( - {name: 'GUILD_NAME', content: group.name}, - {name: 'GUILD_URL', content: '/#/options/groups/guilds/public'} - ); - }else{ - emailVars.push( - {name: 'PARTY_NAME', content: group.name}, - {name: 'PARTY_URL', content: '/#/options/groups/party'} - ) - } - - utils.txnEmail(invite, ('invited-' + (group.type == 'guild' ? 'guild' : 'party')), emailVars); - } - - cb(); - }); - } - }); - }, function(err){ - if(err) return err.code ? res.status(err.code).json({err: err.err}) : next(err); - - async.series([ - function(cb) { - group.save(cb); - }, - function(cb) { - // TODO pass group from save above don't find it again, or you have to find it again in order to run populate? - Group.findById(group._id).populate('leader', nameFields).exec(function (err, savedGroup) { - if (err) return next(err); - savedGroup.getTransformedData({ - cb: function (err, transformedGroup) { - if (err) return next(err); - res.json(transformedGroup); - }, - populateMembers: savedGroup.type === 'party' ? partyFields : nameFields, - populateInvites: nameFields, - populateChallenges: challengeFields, - }) - }); - } - ]); - }); -}; - -var inviteByEmails = function(invites, group, req, res, next){ - var usersAlreadyRegistered = []; - - async.each(invites, function(invite, cb){ - if (invite.email) { - User.findOne({$or: [ - {'auth.local.email': invite.email}, - {'auth.facebook.emails.value': invite.email} - ]}).select({_id: true, 'preferences.emailNotifications': true}) - .exec(function(err, userToContact){ - if(err) return next(err); - - if(userToContact){ - usersAlreadyRegistered.push(userToContact._id); - return cb(); - } - - // yeah, it supports guild too but for backward compatibility we'll use partyInvite as query - - var link = '?partyInvite='+ utils.encrypt(JSON.stringify({id:group._id, inviter:res.locals.user._id, name:group.name})); - - var inviterVars = utils.getUserInfo(res.locals.user, ['name', 'email']); - var variables = [ - {name: 'LINK', content: link}, - {name: 'INVITER', content: req.body.inviter || inviterVars.name} - ]; - - if(group.type == 'guild'){ - variables.push({name: 'GUILD_NAME', content: group.name}); - } - - // TODO implement "users can only be invited once" - // Check for the email address not to be unsubscribed - EmailUnsubscription.findOne({email: invite.email}, function(err, unsubscribed){ - if(err) return cb(err); - if(unsubscribed) return cb(); - - utils.txnEmail(invite, ('invite-friend' + (group.type == 'guild' ? '-guild' : '')), variables); - - cb(); - }); - }); - }else{ - cb(); - } - }, function(err){ - if(err) return err.code ? res.status(err.code).json({err: err.err}) : next(err); - - if (usersAlreadyRegistered.length > 0){ - inviteByUUIDs(usersAlreadyRegistered, group, req, res, next); - } else{ - - // Send only status code down the line because it doesn't need - // info on invited users since they are not yet registered - res.status(200).json({}); - } - }); -}; - -api.invite = function(req, res, next){ - var group = res.locals.group; - let userParty = res.locals.user.party && res.locals.user.party._id; - let userGuilds = res.locals.user.guilds; - - if (group.type === 'party' && userParty !== group._id) { - return res.status(401).json({err: "Only a member can invite new members!"}); - } - - if (group.type === 'guild' && group.privacy === 'private' && !_.contains(userGuilds, group._id)) { - return res.status(401).json({err: "Only a member can invite new members!"}); - } - - if (req.body.uuids) { - inviteByUUIDs(req.body.uuids, group, req, res, next); - } else if (req.body.emails) { - inviteByEmails(req.body.emails, group, req, res, next) - } else { - return res.status(400).json({err: "Can only invite by email or uuid"}); - } -} - -api.removeMember = function(req, res, next){ - var group = res.locals.group; - var uuid = req.query.uuid; - var message = req.query.message; - var user = res.locals.user; - - // Send an email to the removed user with an optional message from the leader - var sendMessage = function(removedUser){ - if(removedUser.preferences.emailNotifications.kickedGroup !== false){ - utils.txnEmail(removedUser, ('kicked-from-' + group.type), [ - {name: 'GROUP_NAME', content: group.name}, - {name: 'MESSAGE', content: message}, - {name: 'GUILDS_LINK', content: '/#/options/groups/guilds/public'}, - {name: 'PARTY_WANTED_GUILD', content: '/#/options/groups/guilds/f2db2a7f-13c5-454d-b3ee-ea1f5089e601'} - ]); - } - } - - if(group.leader !== user._id){ - return res.status(401).json({err: "Only group leader can remove a member!"}); - } - - if(user._id === uuid){ - return res.status(401).json({err: "You cannot remove yourself!"}); - } - - User.findById(uuid, function(err, removedUser){ - if (err) return next(err); - let isMember = group._id === removedUser.party._id || _.contains(removedUser.guilds, group._id); - let isInvited = group._id === removedUser.invitations.party._id || !!_.find(removedUser.invitations.guilds, {id: group._id}); - - if(isMember){ - var update = {}; - if (group.quest && group.quest.leader === uuid) { - update['$set'] = { - quest: { key: null, leader: null } - }; - } else if(group.quest && group.quest.members){ - // remove member from quest - update['$unset'] = {}; - update['$unset']['quest.members.' + uuid] = ""; - } - update['$inc'] = {memberCount: -1}; - Group.update({_id:group._id},update, function(err, saved){ - if (err) return next(err); - - sendMessage(removedUser); - - //Mark removed users messages as seen - var update = {$unset:{}}; - if (group.type === 'guild') { - update.$pull = {guilds: group._id}; - } else { - update.$unset.party = true; - } - update.$unset['newMessages.' + group._id] = ''; - if (group.quest && group.quest.active && group.quest.leader === uuid) { - update['$inc'] = {}; - update['$inc']['items.quests.' + group.quest.key] = 1; - } - User.update({_id: removedUser._id, apiToken: removedUser.apiToken}, update).exec(); - - // Sending an empty 204 because Group.update doesn't return the group - // see http://mongoosejs.com/docs/api.html#model_Model.update - group = uuid = null; - return res.sendStatus(204); - }); - }else if(isInvited){ - var invitations = removedUser.invitations; - if(group.type === 'guild'){ - invitations.guilds.splice(_.indexOf(invitations.guilds, group._id), 1); - }else{ - invitations.party = undefined; - } - - async.series([ - function(cb){ - removedUser.save(cb); - }, - ], function(err, results){ - if (err) return next(err); - - // Sending an empty 204 because Group.update doesn't return the group - // see http://mongoosejs.com/docs/api.html#model_Model.update - sendMessage(removedUser); - group = uuid = null; - return res.sendStatus(204); - }); - }else{ - group = uuid = null; - return res.status(400).json({err: "User not found among group's members!"}); - } - }); -} - -// ------------------------------------ -// Quests -// ------------------------------------ -function canStartQuestAutomatically (group) { - // If all members are either true (accepted) or false (rejected) return true - // If any member is null/undefined (undecided) return false - return _.every(group.quest.members, _.isBoolean); -} - -function questStart(req, res, next) { - var group = res.locals.group; - var force = req.query.force; - - // if (group.quest.active) return res.status(400).json({err:'Quest already began.'}); - // temporarily send error email, until we know more about this issue (then remove below, uncomment above). - if (group.quest.active) return next('Quest already began.'); - - group.markModified('quest'); - - // Not ready yet, wait till everyone's accepted, rejected, or we force-start - var statuses = _.values(group.quest.members); - if (!force && (~statuses.indexOf(undefined) || ~statuses.indexOf(null))) { - return group.save(function(err,saved){ - if (err) return next(err); - res.json(saved); - }) - } - - var parallel = [], - questMembers = {}, - key = group.quest.key, - quest = shared.content.quests[key], - collected = quest.collect ? _.transform(quest.collect, function(m,v,k){m[k]=0}) : {}; - - _.each(group.members, function(m){ - var updates = {$set:{},$inc:{'_v':1}}; - if (m == group.quest.leader) - updates['$inc']['items.quests.'+key] = -1; - if (group.quest.members[m] == true) { - // See https://github.com/HabitRPG/habitrpg/issues/2168#issuecomment-31556322 , we need to *not* reset party.quest.progress.up - //updates['$set']['party.quest'] = Group.cleanQuestProgress({key:key,progress:{collect:collected}}); - updates['$set']['party.quest.key'] = key; - updates['$set']['party.quest.progress.down'] = 0; - updates['$set']['party.quest.progress.collect'] = collected; - updates['$set']['party.quest.completed'] = null; - questMembers[m] = true; - - User.findOne({_id: m}, {pushDevices: 1}, function(err, user){ - pushNotify.sendNotify(user, "HabitRPG", shared.i18n.t('questStarted') + ": "+ quest.text() ); - }); - } else { - updates['$set']['party.quest'] = Group.cleanQuestProgress(); - } - - parallel.push(function(cb2){ - User.update({_id:m},updates,cb2); - }); - }); - - group.quest.active = true; - if (quest.boss) { - group.quest.progress.hp = quest.boss.hp; - if (quest.boss.rage) group.quest.progress.rage = 0; - } else { - group.quest.progress.collect = collected; - } - group.quest.members = questMembers; - group.markModified('quest'); // members & progress.collect are both Mixed types - parallel.push(function(cb2){group.save(cb2)}); - - parallel.push(function(cb){ - // Fetch user.auth to send email, then remove it from data sent to the client - populateQuery(group.type, Group.findById(group._id), 'auth.facebook auth.local').exec(cb); - }); - - async.parallel(parallel,function(err, results){ - if (err) return next(err); - - var lastIndex = results.length -1; - var groupClone = clone(group); - - groupClone.members = results[lastIndex].members; - - // Send quest started email - var usersToEmail = groupClone.members.filter(function(user){ - return ( - user.preferences.emailNotifications.questStarted !== false && - user._id !== res.locals.user._id && - group.quest.members[user._id] == true - ) - }); - - utils.txnEmail(usersToEmail, 'quest-started', [ - {name: 'PARTY_URL', content: '/#/options/groups/party'} - ]); - - _.each(groupClone.members, function(user){ - // Remove sensitive data from what is sent to the public - // but after having sent emails as they are needed - user.auth.facebook = undefined; - user.auth.local = undefined; - }); - - group = null; - - return res.json(groupClone); - }); -} - -api.questAccept = function(req, res, next) { - var group = res.locals.group; - var user = res.locals.user; - var key = req.query.key; - - if (!group || group.type !== 'party') return res.status(400).json({err: "Must be in a party to start quests."}); - - // If ?key=xxx is provided, we're starting a new quest and inviting the party. Otherwise, we're a party member accepting the invitation - if (key) { - var quest = shared.content.quests[key]; - if (!quest) return res.status(404).json({err:'Quest ' + key + ' not found'}); - if (quest.lvl && user.stats.lvl < quest.lvl) return res.status(400).json({err: "You must be level "+quest.lvl+" to begin this quest."}); - if (group.quest.key) return res.status(400).json({err: 'Your party is already on a quest. Try again when the current quest has ended.'}); - if (!user.items.quests[key]) return res.status(400).json({err: "You don't own that quest scroll"}); - - let members; - - User.find({ - 'party._id': group._id, - _id: {$ne: user._id}, - }).select('auth.facebook auth.local preferences.emailNotifications profile.name pushDevices') - .exec().then(membersF => { - members = membersF; - - group.markModified('quest'); - group.quest.key = key; - group.quest.leader = user._id; - group.quest.members = {}; - group.quest.members[user._id] = true; - - user.party.quest.RSVPNeeded = false; - user.party.quest.key = key; - - return User.update({ - 'party._id': group._id, - _id: {$ne: user._id}, - }, { - $set: { - 'party.quest.RSVPNeeded': true, - 'party.quest.key': key, - }, - }, {multi: true}).exec(); - }).then(() => { - _.each(members, (member) => { - group.quest.members[member._id] = null; - }); - - if (canStartQuestAutomatically(group)) { - group.startQuest(user).then(() => { - return Bluebird.all([group.save(), user.save()]) - }) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }) - .catch(next); - - } else { - Bluebird.all([group.save(), user.save()]) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }) - .catch(next); - } - }).catch(next); - - // Party member accepting the invitation - } else { - group.markModified('quest'); - group.quest.members[user._id] = true; - user.party.quest.RSVPNeeded = false; - - if (canStartQuestAutomatically(group)) { - group.startQuest(user).then(() => { - return Bluebird.all([group.save(), user.save()]) - }) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }) - .catch(next); - - } else { - Bluebird.all([group.save(), user.save()]) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }) - .catch(next); - } - } -} - -api.questReject = function(req, res, next) { - var group = res.locals.group; - var user = res.locals.user; - - group.quest.members[user._id] = false; - group.markModified('quest.members'); - - user.party.quest = Group.cleanQuestProgress(); - user.markModified('party.quest'); - - if (canStartQuestAutomatically(group)) { - group.startQuest(user).then(() => { - return Bluebird.all([group.save(), user.save()]) - }) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }) - .catch(next); - - } else { - Bluebird.all([group.save(), user.save()]) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }) - .catch(next); - } -} - -api.questCancel = function(req, res, next){ - var group = res.locals.group; - - group.quest = Group.cleanGroupQuest(); - group.markModified('quest'); - - Bluebird.all([ - group.save(), - User.update( - {'party._id': group._id}, - {$set: {'party.quest': Group.cleanQuestProgress()}}, - {multi: true} - ), - ]).then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }).catch(next); - - // Cancel a quest BEFORE it has begun (i.e., in the invitation stage) - // Quest scroll has not yet left quest owner's inventory so no need to return it. - // Do not wipe quest progress for members because they'll want it to be applied to the next quest that's started. -} - -api.questAbort = function(req, res, next){ - var group = res.locals.group; - - let memberUpdates = User.update({ - 'party._id': group._id, - }, { - $set: {'party.quest': Group.cleanQuestProgress()}, - $inc: {_v: 1}, // TODO update middleware - }, {multi: true}).exec(); - - let questLeaderUpdate = User.update({ - _id: group.quest.leader, - }, { - $inc: { - [`items.quests.${group.quest.key}`]: 1, // give back the quest to the quest leader - }, - }).exec(); - - group.quest = Group.cleanGroupQuest(); - group.markModified('quest'); - - Bluebird.all([group.save(), memberUpdates, questLeaderUpdate]) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }) - .catch(next); -} - -api.questLeave = function(req, res, next) { - // Non-member leave quest while still in progress - var group = res.locals.group; - var user = res.locals.user; - - if (!(group.quest && group.quest.active)) { - return res.status(404).json({ err: 'No active quest to leave' }); - } - - if (!(group.quest.members && group.quest.members[user._id])) { - return res.status(403).json({ err: 'You are not part of the quest' }); - } - - if (group.quest.leader === user._id) { - return res.status(403).json({ err: 'Quest leader cannot leave quest' }); - } - - group.quest.members[user._id] = false; - group.markModified('quest.members'); - - user.party.quest = Group.cleanQuestProgress(); - user.markModified('party.quest'); - - var groupSavePromise = Bluebird.promisify(group.save, {context: group}); - var userSavePromise = Bluebird.promisify(user.save, {context: user}); - - Bluebird.all([groupSavePromise(), userSavePromise()]) - .done(function(values) { - return res.sendStatus(204); - }, function(error) { - return next(error); - }); -} - -function _purgeFlagInfoFromChat(group, user) { - group.chat = _.filter(group.chat, function(message) { return !message.flagCount || message.flagCount < 2; }); - _.each(group.chat, function (message) { - if (message.flags) { - var userHasFlagged = message.flags[user._id]; - message.flags = {}; - - if (userHasFlagged) message.flags[user._id] = userHasFlagged; - } - }); -} diff --git a/website/server/controllers/api-v2/hall.js b/website/server/controllers/api-v2/hall.js deleted file mode 100644 index 05d3bf520c..0000000000 --- a/website/server/controllers/api-v2/hall.js +++ /dev/null @@ -1,89 +0,0 @@ -var _ = require('lodash'); -var nconf = require('nconf'); -var async = require('async'); -var shared = require('../../../../common'); -import { - model as User, -} from '../../models/user'; -import { - model as Group, -} from '../../models/group'; -var api = module.exports; - -api.ensureAdmin = function(req, res, next) { - var user = res.locals.user; - if (!(user.contributor && user.contributor.admin)) return res.status(401).json({err:"You don't have admin access"}); - next(); -} - -api.getHeroes = function(req,res,next) { - User.find({'contributor.level':{$gt:0}}) - .select('contributor backer balance profile.name') - .sort('-contributor.level') - .exec(function(err, users){ - if (err) return next(err); - res.json(users); - }); -} - -api.getPatrons = function(req,res,next){ - var page = req.query.page || 0, - perPage = 50; - User.find({'backer.tier':{$gt:0}}) - .select('contributor backer profile.name') - .sort('-backer.tier') - .skip(page*perPage) - .limit(perPage) - .exec(function(err, users){ - if (err) return next(err); - res.json(users); - }); -} - -api.getHero = function(req,res,next) { - User.findById(req.params.uid) - .select('contributor balance profile.name purchased items') - .select('auth.local.username auth.local.email auth.facebook auth.blocked') - .exec(function(err, user){ - if (err) return next(err) - if (!user) return res.status(400).json({err:'User not found'}); - res.json(user); - }); -} - -api.updateHero = function(req,res,next) { - async.waterfall([ - function(cb){ - User.findById(req.params.uid, cb); - }, - function(member, cb){ - if (!member) return res.status(404).json({err: "User not found"}); - member.balance = req.body.balance || 0; - var newTier = req.body.contributor.level; // tier = level in this context - var oldTier = member.contributor && member.contributor.level || 0; - if (newTier > oldTier) { - member.flags.contributor = true; - var gemsPerTier = {1:3, 2:3, 3:3, 4:4, 5:4, 6:4, 7:4, 8:0, 9:0}; // e.g., tier 5 gives 4 gems. Tier 8 = moderator. Tier 9 = staff - var tierDiff = newTier - oldTier; // can be 2+ tier increases at once - while (tierDiff) { - member.balance += gemsPerTier[newTier] / 4; // balance is in $ - tierDiff--; - newTier--; // give them gems for the next tier down if they weren't aready that tier - } - } - member.contributor = req.body.contributor; - member.purchased.ads = req.body.purchased.ads; - if (member.contributor.level >= 6) member.items.pets['Dragon-Hydra'] = 5; - if (req.body.itemPath && req.body.itemVal - && req.body.itemPath.indexOf('items.') === 0 - && User.schema.paths[req.body.itemPath]) { - shared.dotSet(member, req.body.itemPath, req.body.itemVal); // Sanitization at 5c30944 (deemed unnecessary) - } - if (_.isBoolean(req.body.auth.blocked)) member.auth.blocked = req.body.auth.blocked; - member.save(cb); - } - ], function(err, saved){ - if (err) return next(err); - res.status(204).json({}); - }) -} diff --git a/website/server/controllers/api-v2/members.js b/website/server/controllers/api-v2/members.js deleted file mode 100644 index 232bb9ed73..0000000000 --- a/website/server/controllers/api-v2/members.js +++ /dev/null @@ -1,139 +0,0 @@ -import { - model as groups, - chatDefaults, -} from '../../models/group'; -import { - model as User, -} from '../../models/user'; -let partyFields = require('./groups').partyFields; -var api = module.exports; -var async = require('async'); -var _ = require('lodash'); -var shared = require('../../../../common'); -var utils = require('../../libs/api-v2/utils'); -var nconf = require('nconf'); -var pushNotify = require('./pushNotifications'); - -var fetchMember = function(uuid, restrict){ - return function(cb){ - var q = User.findById(uuid); - if (restrict) q.select(partyFields); - q.exec(function(err, member){ - if (err) return cb(err); - if (!member) return cb({code:404, err: 'User not found'}); - return cb(null, member); - }) - } -} - -var sendErr = function(err, res, next){ - err.code ? res.status(err.code).json({err: err.err}) : next(err); -} - -api.getMember = function(req, res, next) { - fetchMember(req.params.uuid, true)(function(err, member){ - if (err) return sendErr(err, res, next); - res.json(member); - }) -} - -api.sendMessage = function(user, member, data){ - var msg; - if (!data.type) { - msg = data.message - } else { - msg = "`Hello " + member.profile.name + ", " + user.profile.name + " has sent you "; - if (data.type == 'gems') { - var gemAmount = data.gems.amount; - var gemLabel = gemAmount > 1 ? "gems" : "gem"; - msg += gemAmount + " " + gemLabel + "!`"; - } else { - var monthAmount = shared.content.subscriptionBlocks[data.subscription.key].months; - var monthLabel = monthAmount > 1 ? "months" : "month"; - msg += monthAmount + " " + monthLabel + " of subscription!`"; - } - msg += data.message ? data.message : ''; - } - shared.refPush(member.inbox.messages, chatDefaults(msg, user)); - member.inbox.newMessages++; - member._v++; - member.markModified('inbox.messages'); - - shared.refPush(user.inbox.messages, _.defaults({sent:true}, chatDefaults(msg, member))); - user.markModified('inbox.messages'); -} - -api.sendPrivateMessage = function(req, res, next){ - var fetchedMember; - async.waterfall([ - fetchMember(req.params.uuid), - function(member, cb) { - fetchedMember = member; - if (~member.inbox.blocks.indexOf(res.locals.user._id) // can't send message if that user blocked me - || ~res.locals.user.inbox.blocks.indexOf(member._id) // or if I blocked them - || member.inbox.optOut) { // or if they've opted out of messaging - return cb({code: 401, err: "Can't send message to this user."}); - } - api.sendMessage(res.locals.user, member, {message:req.body.message}); - async.parallel([ - function (cb2) { member.save(cb2) }, - function (cb2) { res.locals.user.save(cb2) } - ], cb); - } - ], function(err){ - if (err) return sendErr(err, res, next); - - if(fetchedMember.preferences.emailNotifications.newPM !== false){ - utils.txnEmail(fetchedMember, 'new-pm', [ - {name: 'SENDER', content: utils.getUserInfo(res.locals.user, ['name']).name}, - {name: 'PMS_INBOX_URL', content: '/#/options/groups/inbox'} - ]); - } - - res.sendStatus(200); - }) -} - -api.sendGift = function(req, res, next){ - async.waterfall([ - fetchMember(req.params.uuid), - function(member, cb) { - // Gems - switch (req.body.type) { - case "gems": - var amt = req.body.gems.amount / 4, - user = res.locals.user; - if (member.id == user.id) - return cb({code: 401, err: "Cannot send gems to yourself. Try a subscription instead."}); - if (!amt || amt <=0 || user.balance < amt) - return cb({code: 401, err: "Amount must be within 0 and your current number of gems."}); - member.balance += amt; - user.balance -= amt; - api.sendMessage(user, member, req.body); - - var byUsername = utils.getUserInfo(user, ['name']).name; - - if(member.preferences.emailNotifications.giftedGems !== false){ - utils.txnEmail(member, 'gifted-gems', [ - {name: 'GIFTER', content: byUsername}, - {name: 'X_GEMS_GIFTED', content: req.body.gems.amount} - ]); - } - - pushNotify.sendNotify(member, shared.i18n.t('giftedGems'), shared.i18n.t('giftedGemsInfo', { amount: req.body.gems.amount, name: byUsername })); - - return async.parallel([ - function (cb2) { member.save(cb2) }, - function (cb2) { user.save(cb2) } - ], cb); - case "subscription": - return cb(); - default: - return cb({code:400, err:"Body must contain a gems:{amount,fromBalance} or subscription:{months} object"}); - } - } - ], function(err) { - if (err) return sendErr(err, res, next); - res.sendStatus(200); - }); -} diff --git a/website/server/controllers/api-v2/pushNotifications.js b/website/server/controllers/api-v2/pushNotifications.js deleted file mode 100644 index 6b73816c94..0000000000 --- a/website/server/controllers/api-v2/pushNotifications.js +++ /dev/null @@ -1,6 +0,0 @@ -// TODO move to /api-v2 -var api = module.exports; - -api.sendNotify = function(user, title, msg, timeToLive){ - // Disabled, see libs/api-v3/pushNotifications -}; diff --git a/website/server/controllers/api-v2/unsubscription.js b/website/server/controllers/api-v2/unsubscription.js deleted file mode 100644 index a91db19d86..0000000000 --- a/website/server/controllers/api-v2/unsubscription.js +++ /dev/null @@ -1,40 +0,0 @@ -import { - model as User, -} from '../../models/user'; -import { - model as EmailUnsubscription, -} from '../../models/emailUnsubscription'; -var utils = require('../../libs/api-v2/utils'); -var i18n = require('../../../../common').i18n; - -var api = module.exports = {}; - -api.unsubscribe = function(req, res, next){ - if(!req.query.code) return res.status(500).json({err: 'Missing unsubscription code.'}); - - var data = JSON.parse(utils.decrypt(req.query.code)); - - if(data._id){ - User.update({_id: data._id}, { - $set: {'preferences.emailNotifications.unsubscribeFromAll': true} - }, {multi: false}, function(err, updateRes){ - if(err) return next(err); - if(updateRes.n !== 1) return res.json(404, {err: 'User not found'}); - - res.send('

' + i18n.t('unsubscribedSuccessfully', null, req.language) + '

' + i18n.t('unsubscribedTextUsers', null, req.language)); - }); - }else{ - EmailUnsubscription.findOne({email: data.email}, function(err, doc){ - if(err) return next(err); - var okRes = '

' + i18n.t('unsubscribedSuccessfully', null, req.language) + '

' + i18n.t('unsubscribedTextOthers', null, req.language); - - if(doc) return res.send(okRes); - - EmailUnsubscription.create({email: data.email}, function(err, doc){ - if(err) return next(err); - - res.send(okRes); - }) - }); - } -}; diff --git a/website/server/controllers/api-v2/user.js b/website/server/controllers/api-v2/user.js deleted file mode 100644 index 3812accfb6..0000000000 --- a/website/server/controllers/api-v2/user.js +++ /dev/null @@ -1,1066 +0,0 @@ -var url = require('url'); -var ipn = require('paypal-ipn'); -var _ = require('lodash'); -var validator = require('validator'); -var nconf = require('nconf'); -var asyncM = require('async'); -var shared = require('../../../../common'); -import { - model as User, -} from '../../models/user'; -import { - NotFound, -} from '../../libs/api-v3/errors'; -import { model as Tag } from '../../models/tag'; -import * as Tasks from '../../models/task'; -import Bluebird from 'bluebird'; -import {removeFromArray} from './../../libs/api-v3/collectionManipulators'; -var utils = require('./../../libs/api-v2/utils'); -var analytics = utils.analytics; -import { - basicFields as basicGroupFields, - model as Group, -} from '../../models/group'; -import { - model as Challenge, -} from '../../models/challenge'; -var moment = require('moment'); -var logging = require('./../../libs/api-v2/logging'); -var acceptablePUTPaths; -let restrictedPUTSubPaths; -import v3UserController from '../api-v3/user'; - -let i18n = shared.i18n; - -var api = module.exports; -var webhook = require('../../libs/api-v2/webhook'); - -const partyMembersFields = 'profile.name stats achievements items.special'; - -// api.purchase // Shared.ops - -api.getContent = function(req, res, next) { - var language = 'en'; - - if (typeof req.query.language != 'undefined') - language = req.query.language.toString(); //|| 'en' in i18n - - var content = _.cloneDeep(shared.content); - var walk = function(obj, lang){ - _.each(obj, function(item, key, source){ - if (_.isPlainObject(item) || _.isArray(item)) return walk(item, lang); - if (_.isFunction(item) && item.i18nLangFunc) source[key] = item(lang); - }); - } - walk(content, language); - res.json(content); -} - -api.getModelPaths = function(req,res,next){ - res.json(_.reduce(User.schema.paths,function(m,v,k){ - m[k] = v.instance || 'Boolean'; - return m; - },{})); -} - -/* - ------------------------------------------------------------------------ - Tasks - ------------------------------------------------------------------------ -*/ - - -/* - Local Methods - --------------- -*/ - -var findTask = function(req, res) { - return res.locals.user.tasks[req.params.id]; -}; - -function findTaskByIdOrLegacyId (user, taskId, callback) { - asyncM.waterfall([ - function (cb) { - Tasks.Task.findOne({ - _id: taskId, - userId: user._id, - }, cb); - }, - function (task, cb) { - if (task) return cb(null, task); - - Tasks.Task.findOne({ - _legacyId: taskId, - userId: user._id, - }, cb); - }, - ], callback); -} - -/* - API Routes - --------------- -*/ - -api.score = function(req, res, next) { - var id = req.params.id, - direction = req.params.direction, - user = res.locals.user, - body = req.body || {}, - task; - - // Send error responses for improper API call - if (!id) return res.json(400, {err: ':id required'}); - if (direction !== 'up' && direction !== 'down') { - if (direction == 'unlink' || direction == 'sort') return next(); - return res.json(400, {err: ":direction must be 'up' or 'down'"}); - } - - findTaskByIdOrLegacyId(user, id, function (err, task) { - if (err) return next(err); - - // If exists already, score it - if (!task) { - // If it doesn't exist, this is likely a 3rd party up/down - create a new one, then score it - // Defaults. Other defaults are handled in user.ops.addTask() - var taskOptions = { - type: body.type || 'habit', - text: body.text || id, - userId: user._id, - notes: body.notes || "This task was created by a third-party service. Feel free to edit, it won't harm the connection to that service. Additionally, multiple services may piggy-back off this task." // TODO translate - } - - if (validator.isUUID(id)) { - taskOptions._id = id; // TODO this might easily lead to conflicts as ids are now unique db-wide - } else { - taskOptions._legacyId = id; - } - - task = new Tasks.Task(taskOptions); - - user.tasksOrder[task.type + 's'].unshift(task._id); - } - - // Set completed if type is daily or todo - if (task.type === 'daily' || task.type === 'todo') { - task.completed = direction === 'up'; - } - - var [delta] = shared.ops.scoreTask({ - user, - task, - direction, - }, req); - // Drop system (don't run on the client, as it would only be discarded since ops are sent to the API, not the results) - if (direction === 'up') user.fns.randomDrop({task, delta}, req); - - asyncM.parallel({ - task: task.save.bind(task), - user: user.save.bind(user) - }, function(err, results){ - if(err) return next(err); - - // TODO this is suuuper strange, sometimes results.user is an array, sometimes user directly - var saved = Array.isArray(results.user) ? results.user[0] : results.user; - var task = Array.isArray(results.task) ? results.task[0] : results.task; - - var userStats = saved.toJSON().stats; - var resJsonData = _.extend({ delta: delta, _tmp: user._tmp }, userStats); - res.json(200, resJsonData); - - var webhookData = _generateWebhookTaskData( - task, direction, delta, userStats, user - ); - webhook.sendTaskWebhook(user.preferences.webhooks, webhookData); - - if ( - (!task.challenge.id || task.challenge.broken) // If it's a challenge task, sync the score. Do it in the background, we've already sent down a response and the user doesn't care what happens back there - || (task.type == 'reward') // we don't want to update the reward GP cost - ) return; - - // select name and shortName because they can be synced on syncToUser - Challenge.findById(task.challenge.id, 'name shortName', function(err, chal) { - if (err) return next(err); - if (!chal) { - task.challenge.broken = 'CHALLENGE_DELETED'; - task.save(); - return; - } - - Tasks.Task.findOne({ - '_id': task.challenge.taskId, - userId: {$exists: false} - }, function(err, chalTask){ - if(err) return; //TODO - // this task was removed from the challenge, notify user - if(!chalTask) { - // TODO finish - chal.getTasks(function(err, chalTasks){ - if(err) return; //TODO - chal.syncToUser(user, chalTasks); - }); - } else { - chalTask.value += delta; - if (chalTask.type == 'habit' || chalTask.type == 'daily') - chalTask.history.push({value: chalTask.value, date: +new Date}); - chalTask.save(); - } - }); - }); - }); - }); -}; - -/** - * Get all tasks - */ -api.getTasks = function(req, res, next) { - var user = res.locals.user; - - user.getTasks(req.query.type, function (err, tasks) { - if (err) return next(err); - res.status(200).json(tasks.map(task => task.toJSONV2())); - }); -}; - -/** - * Get Task - */ -api.getTask = function(req, res, next) { - var user = res.locals.user, - id = req.params.id; - - findTaskByIdOrLegacyId(user, id, function (err, task) { - if (err) return next(err); - if (!task) return res.status(404).json({err: shared.i18n.t('messageTaskNotFound')}); - res.status(200).json(task.toJSONV2()); - }); -}; - -/* - ------------------------------------------------------------------------ - Items - ------------------------------------------------------------------------ -*/ -// api.buy // handled in Shard.ops - -api.getBuyList = function (req, res, next) { - var list = shared.updateStore(res.locals.user); - return res.status(200).json(list); -}; - -/* - ------------------------------------------------------------------------ - User - ------------------------------------------------------------------------ -*/ - -/** - * Get User - */ -api.getUser = function(req, res, next) { - res.locals.user.getTransformedData(function(err, user){ - user.stats.toNextLevel = shared.tnl(user.stats.lvl); - user.stats.maxHealth = shared.maxHealth; - user.stats.maxMP = res.locals.user._statsComputed.maxMP; - delete user.apiToken; - if (user.auth && user.auth.local) { - delete user.auth.local.hashed_password; - delete user.auth.local.salt; - } - return res.status(200).json(user); - }); -}; - -/** - * Get anonymized User - */ -api.getUserAnonymized = function(req, res, next) { - res.locals.user.getTransformedData(function(err, user){ - user.stats.toNextLevel = shared.tnl(user.stats.lvl); - user.stats.maxHealth = shared.maxHealth; - user.stats.maxMP = res.locals.user._statsComputed.maxMP; - - delete user.apiToken; - - if (user.auth) { - delete user.auth.local; - delete user.auth.facebook; - } - - delete user.newMessages; - - delete user.profile; - delete user.purchased.plan; - delete user.contributor; - delete user.invitations; - - delete user.items.special.nyeReceived; - delete user.items.special.valentineReceived; - - delete user.webhooks; - delete user.achievements.challenges; - - _.forEach(user.inbox.messages, function(msg){ - msg.text = "inbox message text"; - }); - - _.forEach(user.tags, function(tag){ - tag.name = "tag"; - tag.challenge = "challenge"; - }); - - function cleanChecklist(task){ - var checklistIndex = 0; - - _.forEach(task.checklist, function(c){ - c.text = "item" + checklistIndex++; - }); - } - - _.forEach(user.habits, function(task){ - task.text = "task text"; - task.notes = "task notes"; - }); - - _.forEach(user.rewards, function(task){ - task.text = "task text"; - task.notes = "task notes"; - }); - - _.forEach(user.dailys, function(task){ - task.text = "task text"; - task.notes = "task notes"; - - cleanChecklist(task); - }); - - _.forEach(user.todos, function(task){ - task.text = "task text"; - task.notes = "task notes"; - - cleanChecklist(task); - }); - - return res.status(200).json(user); - }); -}; - -/** - * This tells us for which paths users can call `PUT /user` (or batch-update equiv, which use `User.set()` on our client). - * The trick here is to only accept leaf paths, not root/intermediate paths (see http://goo.gl/OEzkAs) - * TODO - one-by-one we want to widdle down this list, instead replacing each needed set path with API operations - */ -acceptablePUTPaths = _.reduce(require('./../../models/user').schema.paths, (m, v, leaf) => { - let updatablePaths = 'achievements filters flags invitations lastCron party preferences profile stats inbox'.split(' '); - let found = _.find(updatablePaths, (rootPath) => { - return leaf.indexOf(rootPath) === 0; - }); - - if (found) m[leaf] = true; - - return m; -}, {}); - -restrictedPUTSubPaths = 'stats.class'.split(' '); - -_.each(restrictedPUTSubPaths, (removePath) => { - delete acceptablePUTPaths[removePath]; -}); - -let requiresPurchase = { - 'preferences.background': 'background', - 'preferences.shirt': 'shirt', - 'preferences.size': 'size', - 'preferences.skin': 'skin', - 'preferences.chair': 'chair', - 'preferences.hair.bangs': 'hair.bangs', - 'preferences.hair.base': 'hair.base', - 'preferences.hair.beard': 'hair.beard', - 'preferences.hair.color': 'hair.color', - 'preferences.hair.flower': 'hair.flower', - 'preferences.hair.mustache': 'hair.mustache', -}; - -let checkPreferencePurchase = (user, path, item) => { - let itemPath = `${path}.${item}`; - let appearance = _.get(shared.content.appearances, itemPath) - if (!appearance) return false; - if (appearance.price === 0) return true; - - return _.get(user.purchased, itemPath); -}; - -/** - * Update user - * Send up PUT /user as `req.body={path1:val, path2:val, etc}`. Example: - * PUT /user {'stats.hp':50, 'tasks.TASK_ID.repeat.m':false} - * See acceptablePUTPaths for which user paths are supported -*/ -api.update = (req, res, next) => { - let user = res.locals.user; - let errors = []; - - if (_.isEmpty(req.body)) return res.status(200).json(user); - - _.each(req.body, (v, k) => { - let purchasable = requiresPurchase[k]; - - if (purchasable && !checkPreferencePurchase(user, purchasable, v)) { - return errors.push(`Must purchase ${v} to set it on ${k}`); - } - - if (acceptablePUTPaths[k]) { - user.fns.dotSet(k, v); - } else { - errors.push(shared.i18n.t('messageUserOperationProtected', { operation: k })); - } - return true; - }); - - user.save((err) => { - if (!_.isEmpty(errors)) return res.status(401).json({err: errors}); - if (err) { - if (err.name == 'ValidationError') { - let errorMessages = _.map(_.values(err.errors), (error) => { - return error.message; - }); - return res.status(400).json({err: errorMessages}); - } - return next(err); - } - - res.status(200).json(user); - user = errors = null; - }); -}; - -api.cron = require('../../middlewares/api-v3/cron'); - -// api.reroll // Shared.ops -// api.reset // Shared.ops - -api.delete = function(req, res, next) { - var user = res.locals.user; - var plan = user.purchased.plan; - - if (plan && plan.customerId && !plan.dateTerminated){ - return res.status(400).json({err:"You have an active subscription, cancel your plan before deleting your account."}); - } - - let types = ['party', 'guilds']; - let groupFields = basicGroupFields.concat(' leader memberCount'); - - Group.getGroups({user, types, groupFields}) - .then(groups => { - return Bluebird.all(groups.map((group) => { - return group.leave(user, 'remove-all'); - })); - }) - .then(() => { - return Tasks.Task.remove({ - userId: user._id, - }).exec(); - }) - .then(() => { - return user.remove(); - }) - .then(() => { - res.sendStatus(200); - }) - .catch(next); -} - -/* - ------------------------------------------------------------------------ - Development Only Operations - ------------------------------------------------------------------------ - */ -if (nconf.get('NODE_ENV') === 'development') { - - api.addTenGems = function(req, res, next) { - var user = res.locals.user; - - user.balance += 2.5; - - user.save(function(err){ - if (err) return next(err); - res.sendStatus(204); - }); - }; - - api.addHourglass = function(req, res, next) { - var user = res.locals.user; - - user.purchased.plan.consecutive.trinkets += 1; - - user.save(function(err){ - if (err) return next(err); - res.sendStatus(204); - }); - }; -} - -/* - ------------------------------------------------------------------------ - Tags - ------------------------------------------------------------------------ - */ - -api.getTags = function (req, res, next) { - res.json(res.locals.user.tags.toObject().map(tag => { - return { - name: tag.name, - id: tag.id, - challenge: tag.challenge, - } - })); -}; - -api.getTag = function (req, res, next) { - let tag = _.find(res.locals.user.tags, {id: req.params.id}); - if (!tag) { - return res.status(404).json({err: i18n.t('messageTagNotFound', req.language)}); - } - - res.json({ - name: tag.name, - id: tag.id, - challenge: tag.challenge, - }); -}; - -api.addTag = function (req, res, next) { - let user = res.locals.user; - - user.tags.push(Tag.sanitize(req.body)); - user.save(function (err, user) { - if (err) return next(err); - - res.json(user.tags.toObject().map(tag => { - return { - name: tag.name, - id: tag.id, - challenge: tag.challenge, - } - })); - }); -}; - -api.updateTag = function (req, res, next) { - let user = res.locals.user; - - let tag = _.find(res.locals.user.tags, {id: req.params.id}); - if (!tag) { - return res.status(404).json({err: i18n.t('messageTagNotFound', req.language)}); - } - - tag.name = req.body.name; - user.save(function (err, user) { - if (err) return next(err); - - res.json({ - name: tag.name, - id: tag.id, - challenge: tag.challenge, - }); - }); -} - -api.sortTag = function (req, res, next) { - var ref = req.query; - var to = ref.to; - var from = ref.from; - let user = res.locals.user; - - if (!((to != null) && (from != null))) { - return res.statu(500).json('?to=__&from=__ are required'); - } - - user.tags.splice(to, 0, user.tags.splice(from, 1)[0]); - user.save(function (err, user) { - if (err) return next(err); - - res.json(user.tags.toObject().map(tag => { - return { - name: tag.name, - id: tag.id, - challenge: tag.challenge, - } - })); - }); -} - -api.deleteTag = function (req, res, next) { - let user = res.locals.user; - - let tag = removeFromArray(user.tags, { id: req.params.id }); - if (!tag) { - return res.status(404).json({err: i18n.t('messageTagNotFound', req.language)}); - } - - - Tasks.Task.update({ - userId: user._id, - }, { - $pull: { - tags: tag.id, - }, - }, {multi: true}).exec(); - - user.save(function (err, user) { - if (err) return next(err); - - res.json(user.tags.toObject().map(tag => { - return { - name: tag.name, - id: tag.id, - challenge: tag.challenge, - } - })); - }); -} - -/* - ------------------------------------------------------------------------ - Spells - ------------------------------------------------------------------------ - */ -api.cast = async function(req, res, next) { - try { - let user = res.locals.user; - let spellId = req.params.spell; - let targetId = req.query.targetId; - - if (spellId === 'heallAll') { - spellId = 'healAll'; - } else if (spellId === 'spookDust') { - spellId = 'spookySparkles'; - } - - let klass = shared.content.spells.special[spellId] ? 'special' : user.stats.class; - let spell = shared.content.spells[klass][spellId]; - - if (!spell) return res.status(404).json({err: 'Spell "' + req.params.spell + '" not found.'}); - if (spell.mana > user.stats.mp) return res.status(400).json({err: 'Not enough mana to cast spell'}); - - let targetType = spell.target; - - if (targetType === 'task') { - let task = await Tasks.Task.findOne({ - _id: targetId, - userId: user._id, - }).exec(); - if (!task) { - return res.status(404).json({err: 'Task "' + targetId + '" not found.'}); - } - if (task.challenge.id) return res.status(40).json({err: 'Cannot cast spell on challenge task.'}); - - - spell.cast(user, task, req); - await task.save(); - } else if (targetType === 'self') { - spell.cast(user, null, req); - await user.save(); - } else if (targetType === 'tasks') { // new target type when all the user's tasks are necessary - let tasks = await Tasks.Task.find({ - userId: user._id, - 'challenge.id': {$exists: false}, // exclude challenge tasks - $or: [ // Exclude completed todos - {type: 'todo', completed: false}, - {type: {$in: ['habit', 'daily', 'reward']}}, - ], - }).exec(); - - spell.cast(user, tasks, req); - - let toSave = tasks.filter(t => t.isModified()); - let isUserModified = user.isModified(); - toSave.unshift(user.save()); - let saved = await Bluebird.all(toSave); - } else if (targetType === 'party' || targetType === 'user') { - let party = await Group.getGroup({groupId: 'party', user}); - // arrays of users when targetType is 'party' otherwise single users - let partyMembers; - - if (targetType === 'party') { - if (!party) { - partyMembers = [user]; // Act as solo party - } else { - partyMembers = await User.find({'party._id': party._id}).select(partyMembersFields).exec(); - } - - spell.cast(user, partyMembers, req); - await Bluebird.all(partyMembers.map(m => m.save())); - } else { - if (!party && (!targetId || user._id === targetId)) { - partyMembers = user; - } else { - partyMembers = await User.findOne({_id: targetId, 'party._id': party._id}).select(partyMembersFields).exec(); - } - - if (!partyMembers) throw new NotFound(res.t('userWithIDNotFound', {userId: targetId})); - spell.cast(user, partyMembers, req); - if (partyMembers === user) { - await partyMembers.save(); - } else { - await Bluebird.all([ - await partyMembers.save(), - await user.save(), - ]); - } - } - - if (party && !spell.silent) { - let message = `\`${user.profile.name} casts ${spell.text()}${targetType === 'user' ? ` on ${partyMembers.profile.name}` : ' for the party'}.\``; - party.sendChat(message); - await party.save(); - } - } - - user.getTransformedData(function (err, transformedUser) { - if (err) next(err); - res.json(transformedUser); - }); - } catch (e) { - return res.status(500).json({err: 'An error happened'}); - } -} - -// It supports guild too now but we'll stick to partyInvite for backward compatibility -api.sessionPartyInvite = function(req,res,next){ - if (!req.session.partyInvite) return next(); - var inv = res.locals.user.invitations; - if (inv.party && inv.party.id) return next(); // already invited to a party - asyncM.waterfall([ - function(cb){ - Group.findOne({_id:req.session.partyInvite.id, members:{$in:[req.session.partyInvite.inviter]}}) - .select('invites members type').exec(cb); - }, - function(group, cb){ - if (!group){ - // Don't send error as it will prevent users from using the site - delete req.session.partyInvite; - return cb(); - } - - if (group.type == 'guild'){ - inv.guilds.push(req.session.partyInvite); - } else{ - //req.body.type in 'guild', 'party' - inv.party = req.session.partyInvite; - } - inv.party = req.session.partyInvite; - delete req.session.partyInvite; - if (!~group.invites.indexOf(res.locals.user._id)) - group.invites.push(res.locals.user._id); //$addToSt - group.save(cb); - }, - function(saved, cb){ - res.locals.user.save(cb); - } - ], next); -} - -api.clearCompleted = function(req, res, next) { - var user = res.locals.user; - - Tasks.Task.remove({ - userId: user._id, - type: 'todo', - completed: true, - 'challenge.id': {$exists: false}, - }, function (err) { - if (err) return next(err); - - Tasks.Task.find({ - userId: user._id, - type: 'todo', - completed: false, - }, function (err, uncompleted) { - if (err) return next(err); - res.json(uncompleted); - }); - }); -}; - -api.sortTask = async function (req, res, next) { - try { - let user = res.locals.user; - let to = Number(req.query.to); - - let task = await Tasks.Task.findOne({ - _id: req.params.id, - userId: user._id, - }).exec(); - - if (!task) return res.status(404).json(i18n.t('messageTaskNotFound', req.language)); - if (task.type !== 'todo' || !task.completed) { - let order = user.tasksOrder[`${task.type}s`]; - let currentIndex = order.indexOf(task._id); - - // If for some reason the task isn't ordered (should never happen), push it in the new position - // if the task is moved to a non existing position - // or if the task is moved to position -1 (push to bottom) - // -> push task at end of list - if (!order[to] && to !== -1) { - order.push(task._id); - } else { - if (currentIndex !== -1) order.splice(currentIndex, 1); - if (to === -1) { - order.push(task._id); - } else { - order.splice(to, 0, task._id); - } - } - await user.save(); - } - - user.getTasks(function (err, userTasks) { - if(err) return next(err); - res.json(userTasks); - }); - } catch (e) { - res.status(500).json({err: 'An error happened.'}); - } -} - -api.deleteTask = function(req, res, next) { - var user = res.locals.user; - if(!req.params || !req.params.id) return res.json(404, shared.i18n.t('messageTaskNotFound', req.language)); - - var id = req.params.id; - // Try removing from all orders since we don't know the task's type - var removeTaskFromOrder = function(array) { - removeFromArray(array, id); - }; - - ['habits', 'dailys', 'todos', 'rewards'].forEach(function (type){ - removeTaskFromOrder(user.tasksOrder[type]) - }); - - asyncM.parallel({ - user: user.save.bind(user), - task: function(cb) { - Tasks.Task.remove({_id: id, userId: user._id}, cb); - } - }, function(err, results) { - if(err) return next(err); - - if(results.task.result.n < 1){ - return res.status(404).json({err: shared.i18n.t('messageTaskNotFound', req.language)}) - } - - res.status(200).json({}); - }); -}; - -api.updateTask = function(req, res, next) { - var user = res.locals.user, - id = req.params.id; - - req.body = Tasks.Task.fromJSONV2(req.body); - - findTaskByIdOrLegacyId(user, id, function (err, task) { - if(err) return next(err); - if(!task) return res.status(404).json({err: 'Task not found.'}) - - try { - // we have to convert task to an object because otherwise things don't get merged correctly. Bad for performances? - let [updatedTaskObj] = shared.ops.updateTask(task.toObject(), req); - - // Sanitize differently user tasks linked to a challenge - let sanitizedObj; - - if (task.userId && task.challenge && task.challenge.id) { - sanitizedObj = Tasks.Task.sanitizeUserChallengeTask(updatedTaskObj); - } else { - sanitizedObj = Tasks.Task.sanitize(updatedTaskObj); - } - - _.assign(task, sanitizedObj); - - task.save(function(err, task){ - if(err) return next(err); - - return res.json(task.toJSONV2()); - }); - } catch (err) { - return res.status(err.code).json({err: err.message}); - } - }); -}; - -api.addTask = function(req, res, next) { - var user = res.locals.user; - req.body.type = req.body.type || 'habit'; - req.body.text = req.body.text || 'text'; - req.body = Tasks.Task.fromJSONV2(req.body); - - var task = new Tasks[req.body.type](Tasks.Task.sanitize(req.body)); - - task.userId = user._id; - user.tasksOrder[task.type + 's'].unshift(task._id); - - // Validate that the task is valid and throw if it isn't - // otherwise since we're saving user/challenge and task in parallel it could save the user/challenge with a tasksOrder that doens't match reality - let validationErrors = task.validateSync(); - if (validationErrors) return next(validationErrors); - - Bluebird.all([ - user.save(), - task.save({validateBeforeSave: false}) // already done ^ - ]).then(results => { - res.status(200).json(results[1].toJSONV2()); - }).catch(next); -}; - -/** - * All other user.ops which can easily be mapped to common/script/index.js, not requiring custom API-wrapping - */ -_.each(shared.ops, function(op,k){ - var kv3; - - if (['rebirth', 'reroll', 'reset'].indexOf(k) !== -1) { // proxy ops that change tasks directly to v3 - if (k === 'rebirth') kv3 = 'userRebirth'; // the name is different in v3 - if (k === 'reroll') kv3 = 'userReroll'; - if (k === 'reset') kv3 = 'userReset'; - - api[k] = function (req, res, next) { - req.v2 = true; - v3UserController[kv3].handler(req, res, next).catch(next); - } - } else if (!api[k]) { - api[k] = function(req, res, next) { - var opResponse; - try { - req.v2 = true; // Used to indicate to the shared code that the old response data should be returned - opResponse = shared.ops[k](res.locals.user, req, analytics); - if (Array.isArray(opResponse) && opResponse.length < 3) { - opResponse = opResponse[0]; - } - } catch (err) { - if (!err.code) return next(err); - if (err.code >= 400) return res.status(err.code).json({err:err.message}); - } - - // If we want to send something other than 500, pass err as {code: 200, message: "Not enough GP"} - res.locals.user.save(function(err){ - if (err) return next(err); - if (opResponse === res.locals.user) { // add tasks - res.locals.user.getTransformedData(function (err, transformedUser) { - if (err) return next(err); - res.status(200).json(transformedUser); - }); - } else { - res.status(200).json(opResponse); - } - }); - } - } -}) - -/* - ------------------------------------------------------------------------ - Batch Update - Run a bunch of updates all at once - ------------------------------------------------------------------------ -*/ -api.batchUpdate = function(req, res, next) { - if (_.isEmpty(req.body)) req.body = []; // cases of {} or null - if (req.body[0] && req.body[0].data) - return res.status(501).json({err: "API has been updated, please refresh your browser or upgrade your mobile app."}) - - var user = res.locals.user; - var oldSend = res.send; - var oldJson = res.json; - - // Stash user.save, we'll queue the save op till the end (so we don't overload the server) - //var oldSave = user.save; - //user.save = function(cb){cb(null,user)} - - // Setup the array of functions we're going to call in parallel with async - res.locals.ops = []; - var ops = _.transform(req.body, function(m,_req){ - if (_.isEmpty(_req)) return; - _req.language = req.language; - - m.push(function() { - var cb = arguments[arguments.length-1]; - res.locals.ops.push(_req); - res.send = res.json = function(code, data) { - if (_.isNumber(code) && code >= 500) - return cb(code+": "+ (data.message ? data.message : data.err ? data.err : JSON.stringify(data))); - return cb(); - }; - if(!api[_req.op]) { return cb(shared.i18n.t('messageUserOperationNotFound', { operation: _req.op })); } - - api[_req.op](_req, res, cb); - }); - }) - // Finally, save user at the end - .concat(/*function(){ - user.save = oldSave; - user.save(arguments[arguments.length-1]); - }*/); - - // call all the operations, then return the user object to the requester - asyncM.waterfall(ops, function(err) { - res.json = oldJson; - res.send = oldSend; - if (err) return next(err); - - var response; - - // return only drops & streaks - if (user._tmp && user._tmp.drop){ - response = user.toJSON(); - res.status(200).json({_tmp: {drop: response._tmp.drop}, _v: response._v}); - - // Fetch full user object - } else if (res.locals.wasModified){ - // Preen 3-day past-completed To-Dos from Angular & mobile app - user.getTransformedData(function(err, transformedData){ - if (err) next(err); - response = transformedData; - - response.todos = shared.preenTodos(response.todos); - response.wasModified = true; - res.status(200).json(response); - }); - // return only the version number - } else{ - response = user.toJSON(); - res.status(200).json({_v: response._v}); - } - - //user.fns.nullify(); - user = res.locals.user = oldSend = oldJson = null; - }); -}; - -function _generateWebhookTaskData(task, direction, delta, stats, user) { - var extendedStats = _.extend(stats, { - toNextLevel: shared.tnl(user.stats.lvl), - maxHealth: shared.maxHealth, - maxMP: user._statsComputed.maxMP - }); - - var userData = { - _id: user._id, - _tmp: user._tmp, - stats: extendedStats - }; - - var taskData = { - details: task, - direction: direction, - delta: delta - } - - return { - task: taskData, - user: userData - } -} diff --git a/website/server/controllers/api-v3/auth.js b/website/server/controllers/api-v3/auth.js index c6671a39b2..cbaedf778b 100644 --- a/website/server/controllers/api-v3/auth.js +++ b/website/server/controllers/api-v3/auth.js @@ -4,22 +4,22 @@ import passport from 'passport'; import nconf from 'nconf'; import { authWithHeaders, -} from '../../middlewares/api-v3/auth'; +} from '../../middlewares/auth'; import { NotAuthorized, BadRequest, NotFound, -} from '../../libs/api-v3/errors'; +} from '../../libs/errors'; import Bluebird from 'bluebird'; -import * as passwordUtils from '../../libs/api-v3/password'; -import logger from '../../libs/api-v3/logger'; +import * as passwordUtils from '../../libs/password'; +import logger from '../../libs/logger'; import { model as User } from '../../models/user'; import { model as Group } from '../../models/group'; import { model as EmailUnsubscription } from '../../models/emailUnsubscription'; -import { sendTxn as sendTxnEmail } from '../../libs/api-v3/email'; -import { decrypt } from '../../libs/api-v3/encryption'; -import { send as sendEmail } from '../../libs/api-v3/email'; -import pusher from '../../libs/api-v3/pusher'; +import { sendTxn as sendTxnEmail } from '../../libs/email'; +import { decrypt } from '../../libs/encryption'; +import { send as sendEmail } from '../../libs/email'; +import pusher from '../../libs/pusher'; let api = {}; diff --git a/website/server/controllers/api-v3/challenges.js b/website/server/controllers/api-v3/challenges.js index a5c42e8b98..60a73b1a19 100644 --- a/website/server/controllers/api-v3/challenges.js +++ b/website/server/controllers/api-v3/challenges.js @@ -1,4 +1,4 @@ -import { authWithHeaders, authWithSession } from '../../middlewares/api-v3/auth'; +import { authWithHeaders, authWithSession } from '../../middlewares/auth'; import _ from 'lodash'; import { model as Challenge } from '../../models/challenge'; import { @@ -13,10 +13,10 @@ import { import { NotFound, NotAuthorized, -} from '../../libs/api-v3/errors'; +} from '../../libs/errors'; import * as Tasks from '../../models/task'; import Bluebird from 'bluebird'; -import csvStringify from '../../libs/api-v3/csvStringify'; +import csvStringify from '../../libs/csvStringify'; let api = {}; diff --git a/website/server/controllers/api-v3/chat.js b/website/server/controllers/api-v3/chat.js index 379f3e398b..365dac2ba1 100644 --- a/website/server/controllers/api-v3/chat.js +++ b/website/server/controllers/api-v3/chat.js @@ -1,14 +1,14 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import { authWithHeaders } from '../../middlewares/auth'; import { model as Group } from '../../models/group'; import { model as User } from '../../models/user'; import { NotFound, NotAuthorized, -} from '../../libs/api-v3/errors'; +} from '../../libs/errors'; import _ from 'lodash'; -import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; -import { getUserInfo, getGroupUrl, sendTxn } from '../../libs/api-v3/email'; -import pusher from '../../libs/api-v3/pusher'; +import { removeFromArray } from '../../libs/collectionManipulators'; +import { getUserInfo, getGroupUrl, sendTxn } from '../../libs/email'; +import pusher from '../../libs/pusher'; import nconf from 'nconf'; import Bluebird from 'bluebird'; diff --git a/website/server/controllers/api-v3/content.js b/website/server/controllers/api-v3/content.js index 9d97198173..02b950cd8f 100644 --- a/website/server/controllers/api-v3/content.js +++ b/website/server/controllers/api-v3/content.js @@ -1,10 +1,10 @@ import common from '../../../../common'; import _ from 'lodash'; -import { langCodes } from '../../libs/api-v3/i18n'; +import { langCodes } from '../../libs/i18n'; import Bluebird from 'bluebird'; import fsCallback from 'fs'; import path from 'path'; -import logger from '../../libs/api-v3/logger'; +import logger from '../../libs/logger'; // Transform fs methods that accept callbacks in ones that return promises const fs = { diff --git a/website/server/controllers/api-v3/coupon.js b/website/server/controllers/api-v3/coupon.js index f4bd6bb6cf..3d7db33459 100644 --- a/website/server/controllers/api-v3/coupon.js +++ b/website/server/controllers/api-v3/coupon.js @@ -1,9 +1,9 @@ -import csvStringify from '../../libs/api-v3/csvStringify'; +import csvStringify from '../../libs/csvStringify'; import { authWithHeaders, authWithSession, -} from '../../middlewares/api-v3/auth'; -import { ensureSudo } from '../../middlewares/api-v3/ensureAccessRight'; +} from '../../middlewares/auth'; +import { ensureSudo } from '../../middlewares/ensureAccessRight'; import { model as Coupon } from '../../models/coupon'; import _ from 'lodash'; import couponCode from 'coupon-code'; diff --git a/website/server/controllers/api-v3/debug.js b/website/server/controllers/api-v3/debug.js index 8533d995ff..67d38bef49 100644 --- a/website/server/controllers/api-v3/debug.js +++ b/website/server/controllers/api-v3/debug.js @@ -1,6 +1,6 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; -import ensureDevelpmentMode from '../../middlewares/api-v3/ensureDevelpmentMode'; -import { BadRequest } from '../../libs/api-v3/errors'; +import { authWithHeaders } from '../../middlewares/auth'; +import ensureDevelpmentMode from '../../middlewares/ensureDevelpmentMode'; +import { BadRequest } from '../../libs/errors'; import { content } from '../../../../common'; import _ from 'lodash'; diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js index d2bb820370..e126fc159e 100644 --- a/website/server/controllers/api-v3/groups.js +++ b/website/server/controllers/api-v3/groups.js @@ -1,4 +1,4 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import { authWithHeaders } from '../../middlewares/auth'; import Bluebird from 'bluebird'; import _ from 'lodash'; import { @@ -15,12 +15,12 @@ import { NotFound, BadRequest, NotAuthorized, -} from '../../libs/api-v3/errors'; -import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; -import { sendTxn as sendTxnEmail } from '../../libs/api-v3/email'; -import { encrypt } from '../../libs/api-v3/encryption'; -import sendPushNotification from '../../libs/api-v3/pushNotifications'; -import pusher from '../../libs/api-v3/pusher'; +} from '../../libs/errors'; +import { removeFromArray } from '../../libs/collectionManipulators'; +import { sendTxn as sendTxnEmail } from '../../libs/email'; +import { encrypt } from '../../libs/encryption'; +import sendPushNotification from '../../libs/pushNotifications'; +import pusher from '../../libs/pusher'; let api = {}; diff --git a/website/server/controllers/api-v3/hall.js b/website/server/controllers/api-v3/hall.js index cf589eef84..218fe5291a 100644 --- a/website/server/controllers/api-v3/hall.js +++ b/website/server/controllers/api-v3/hall.js @@ -1,9 +1,9 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; -import { ensureAdmin } from '../../middlewares/api-v3/ensureAccessRight'; +import { authWithHeaders } from '../../middlewares/auth'; +import { ensureAdmin } from '../../middlewares/ensureAccessRight'; import { model as User } from '../../models/user'; import { NotFound, -} from '../../libs/api-v3/errors'; +} from '../../libs/errors'; import _ from 'lodash'; let api = {}; diff --git a/website/server/controllers/api-v3/members.js b/website/server/controllers/api-v3/members.js index 01aa509c85..dbd8a729dc 100644 --- a/website/server/controllers/api-v3/members.js +++ b/website/server/controllers/api-v3/members.js @@ -1,4 +1,4 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import { authWithHeaders } from '../../middlewares/auth'; import { model as User, publicFields as memberFields, @@ -9,14 +9,14 @@ import { model as Challenge } from '../../models/challenge'; import { NotFound, NotAuthorized, -} from '../../libs/api-v3/errors'; +} from '../../libs/errors'; import * as Tasks from '../../models/task'; import { getUserInfo, sendTxn as sendTxnEmail, -} from '../../libs/api-v3/email'; +} from '../../libs/email'; import Bluebird from 'bluebird'; -import sendPushNotification from '../../libs/api-v3/pushNotifications'; +import sendPushNotification from '../../libs/pushNotifications'; let api = {}; diff --git a/website/server/controllers/api-v3/notifications.js b/website/server/controllers/api-v3/notifications.js index 7130b1bc56..2fda343ac7 100644 --- a/website/server/controllers/api-v3/notifications.js +++ b/website/server/controllers/api-v3/notifications.js @@ -1,8 +1,8 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import { authWithHeaders } from '../../middlewares/auth'; import _ from 'lodash'; import { NotFound, -} from '../../libs/api-v3/errors'; +} from '../../libs/errors'; let api = {}; diff --git a/website/server/controllers/api-v3/pushNotifications.js b/website/server/controllers/api-v3/pushNotifications.js index 4c55b60455..c0ed3506f2 100644 --- a/website/server/controllers/api-v3/pushNotifications.js +++ b/website/server/controllers/api-v3/pushNotifications.js @@ -1,8 +1,8 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import { authWithHeaders } from '../../middlewares/auth'; import { NotAuthorized, NotFound, -} from '../../libs/api-v3/errors'; +} from '../../libs/errors'; let api = {}; diff --git a/website/server/controllers/api-v3/quests.js b/website/server/controllers/api-v3/quests.js index 60f1cfb397..bd07afb9f6 100644 --- a/website/server/controllers/api-v3/quests.js +++ b/website/server/controllers/api-v3/quests.js @@ -1,7 +1,7 @@ import _ from 'lodash'; import Bluebird from 'bluebird'; -import { authWithHeaders } from '../../middlewares/api-v3/auth'; -import analytics from '../../libs/api-v3/analyticsService'; +import { authWithHeaders } from '../../middlewares/auth'; +import analytics from '../../libs/analyticsService'; import { model as Group, } from '../../models/group'; @@ -10,13 +10,13 @@ import { NotFound, NotAuthorized, BadRequest, -} from '../../libs/api-v3/errors'; +} from '../../libs/errors'; import { getUserInfo, sendTxn as sendTxnEmail, -} from '../../libs/api-v3/email'; +} from '../../libs/email'; import common from '../../../../common'; -import sendPushNotification from '../../libs/api-v3/pushNotifications'; +import sendPushNotification from '../../libs/pushNotifications'; const questScrolls = common.content.quests; diff --git a/website/server/controllers/api-v3/shops.js b/website/server/controllers/api-v3/shops.js index 1fa131c516..b9f4150ebb 100644 --- a/website/server/controllers/api-v3/shops.js +++ b/website/server/controllers/api-v3/shops.js @@ -1,4 +1,4 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import { authWithHeaders } from '../../middlewares/auth'; import { shops } from '../../../../common/'; let api = {}; diff --git a/website/server/controllers/api-v3/tags.js b/website/server/controllers/api-v3/tags.js index 88e0e6f1c0..a7c2a6ae5e 100644 --- a/website/server/controllers/api-v3/tags.js +++ b/website/server/controllers/api-v3/tags.js @@ -1,11 +1,11 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import { authWithHeaders } from '../../middlewares/auth'; import { model as Tag } from '../../models/tag'; import * as Tasks from '../../models/task'; import { NotFound, -} from '../../libs/api-v3/errors'; +} from '../../libs/errors'; import _ from 'lodash'; -import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; +import { removeFromArray } from '../../libs/collectionManipulators'; let api = {}; diff --git a/website/server/controllers/api-v3/tasks.js b/website/server/controllers/api-v3/tasks.js index 2b96d860de..23e2b98007 100644 --- a/website/server/controllers/api-v3/tasks.js +++ b/website/server/controllers/api-v3/tasks.js @@ -1,6 +1,6 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; -import { sendTaskWebhook } from '../../libs/api-v3/webhook'; -import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; +import { authWithHeaders } from '../../middlewares/auth'; +import { sendTaskWebhook } from '../../libs/webhook'; +import { removeFromArray } from '../../libs/collectionManipulators'; import * as Tasks from '../../models/task'; import { model as Challenge } from '../../models/challenge'; import { model as Group } from '../../models/group'; @@ -8,11 +8,11 @@ import { NotFound, NotAuthorized, BadRequest, -} from '../../libs/api-v3/errors'; +} from '../../libs/errors'; import common from '../../../../common'; import Bluebird from 'bluebird'; import _ from 'lodash'; -import logger from '../../libs/api-v3/logger'; +import logger from '../../libs/logger'; let api = {}; diff --git a/website/server/controllers/api-v3/user.js b/website/server/controllers/api-v3/user.js index 13153f0da6..abd6d57924 100644 --- a/website/server/controllers/api-v3/user.js +++ b/website/server/controllers/api-v3/user.js @@ -1,10 +1,10 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import { authWithHeaders } from '../../middlewares/auth'; import common from '../../../../common'; import { NotFound, BadRequest, NotAuthorized, -} from '../../libs/api-v3/errors'; +} from '../../libs/errors'; import * as Tasks from '../../models/task'; import { basicFields as basicGroupFields, @@ -13,7 +13,7 @@ import { import { model as User } from '../../models/user'; import Bluebird from 'bluebird'; import _ from 'lodash'; -import * as passwordUtils from '../../libs/api-v3/password'; +import * as passwordUtils from '../../libs/password'; let api = {}; diff --git a/website/server/controllers/top-level/dataexport.js b/website/server/controllers/top-level/dataexport.js index 860e3205d4..c042198a11 100644 --- a/website/server/controllers/top-level/dataexport.js +++ b/website/server/controllers/top-level/dataexport.js @@ -1,21 +1,21 @@ -import { authWithSession } from '../../middlewares/api-v3/auth'; +import { authWithSession } from '../../middlewares/auth'; import { model as User } from '../../models/user'; import * as Tasks from '../../models/task'; import { NotFound, -} from '../../libs/api-v3/errors'; +} from '../../libs/errors'; import _ from 'lodash'; -import csvStringify from '../../libs/api-v3/csvStringify'; +import csvStringify from '../../libs/csvStringify'; import moment from 'moment'; import js2xml from 'js2xmlparser'; import Pageres from 'pageres'; import nconf from 'nconf'; import got from 'got'; import Bluebird from 'bluebird'; -import locals from '../../middlewares/api-v3/locals'; +import locals from '../../middlewares/locals'; import { S3, -} from '../../libs/api-v3/aws'; +} from '../../libs/aws'; const S3_BUCKET = nconf.get('S3:bucket'); diff --git a/website/server/controllers/top-level/email.js b/website/server/controllers/top-level/email.js index b84f54c6e1..7a0d08212f 100644 --- a/website/server/controllers/top-level/email.js +++ b/website/server/controllers/top-level/email.js @@ -1,9 +1,9 @@ import { model as User } from '../../models/user'; import { model as EmailUnsubscription } from '../../models/emailUnsubscription'; -import { decrypt } from '../../libs/api-v3/encryption'; +import { decrypt } from '../../libs/encryption'; import { NotFound, -} from '../../libs/api-v3/errors'; +} from '../../libs/errors'; let api = {}; diff --git a/website/server/controllers/top-level/pages.js b/website/server/controllers/top-level/pages.js index 67e8381701..b68f2976c4 100644 --- a/website/server/controllers/top-level/pages.js +++ b/website/server/controllers/top-level/pages.js @@ -1,4 +1,4 @@ -import locals from '../../middlewares/api-v3/locals'; +import locals from '../../middlewares/locals'; import _ from 'lodash'; import markdownIt from 'markdown-it'; @@ -29,7 +29,7 @@ api.getFrontPage = { }, }; -let staticPages = ['front', 'privacy', 'terms', 'api-v2', 'features', +let staticPages = ['front', 'privacy', 'terms', 'features', 'videos', 'contact', 'plans', 'new-stuff', 'community-guidelines', 'old-news', 'press-kit', 'faq', 'overview', 'apps', 'clear-browser-data', 'merch', 'maintenance-info']; diff --git a/website/server/controllers/top-level/payments/amazon.js b/website/server/controllers/top-level/payments/amazon.js index 972fd5a261..413fdc8e5c 100644 --- a/website/server/controllers/top-level/payments/amazon.js +++ b/website/server/controllers/top-level/payments/amazon.js @@ -1,14 +1,14 @@ import { BadRequest, NotAuthorized, -} from '../../../libs/api-v3/errors'; -import amzLib from '../../../libs/api-v3/amazonPayments'; +} from '../../../libs/errors'; +import amzLib from '../../../libs/amazonPayments'; import { authWithHeaders, authWithUrl, -} from '../../../middlewares/api-v3/auth'; +} from '../../../middlewares/auth'; import shared from '../../../../../common'; -import payments from '../../../libs/api-v3/payments'; +import payments from '../../../libs/payments'; import moment from 'moment'; import { model as Coupon } from '../../../models/coupon'; import { model as User } from '../../../models/user'; diff --git a/website/server/controllers/top-level/payments/iap.js b/website/server/controllers/top-level/payments/iap.js index d894aaacf9..ee013e9e2e 100644 --- a/website/server/controllers/top-level/payments/iap.js +++ b/website/server/controllers/top-level/payments/iap.js @@ -1,14 +1,14 @@ import { authWithHeaders, authWithUrl, -} from '../../../middlewares/api-v3/auth'; -import iap from '../../../libs/api-v3/inAppPurchases'; -import payments from '../../../libs/api-v3/payments'; +} from '../../../middlewares/auth'; +import iap from '../../../libs/inAppPurchases'; +import payments from '../../../libs/payments'; import { NotAuthorized, -} from '../../../libs/api-v3/errors'; +} from '../../../libs/errors'; import { model as IapPurchaseReceipt } from '../../../models/iapPurchaseReceipt'; -import logger from '../../../libs/api-v3/logger'; +import logger from '../../../libs/logger'; let api = {}; diff --git a/website/server/controllers/top-level/payments/paypal.js b/website/server/controllers/top-level/payments/paypal.js index 0bc7b45ef3..c20260b1ea 100644 --- a/website/server/controllers/top-level/payments/paypal.js +++ b/website/server/controllers/top-level/payments/paypal.js @@ -3,7 +3,7 @@ import nconf from 'nconf'; import moment from 'moment'; import _ from 'lodash'; -import payments from '../../../libs/api-v3/payments'; +import payments from '../../../libs/payments'; import ipn from 'paypal-ipn'; import paypal from 'paypal-rest-sdk'; import shared from '../../../../../common'; @@ -14,11 +14,11 @@ import { model as User } from '../../../models/user'; import { authWithUrl, authWithSession, -} from '../../../middlewares/api-v3/auth'; +} from '../../../middlewares/auth'; import { BadRequest, NotAuthorized, -} from '../../../libs/api-v3/errors'; +} from '../../../libs/errors'; const BASE_URL = nconf.get('BASE_URL'); diff --git a/website/server/controllers/top-level/payments/stripe.js b/website/server/controllers/top-level/payments/stripe.js index 3adfd308c8..eb2c27f1e0 100644 --- a/website/server/controllers/top-level/payments/stripe.js +++ b/website/server/controllers/top-level/payments/stripe.js @@ -3,16 +3,16 @@ import shared from '../../../../../common'; import { BadRequest, NotAuthorized, -} from '../../../libs/api-v3/errors'; +} from '../../../libs/errors'; import { model as Coupon } from '../../../models/coupon'; -import payments from '../../../libs/api-v3/payments'; +import payments from '../../../libs/payments'; import nconf from 'nconf'; import { model as User } from '../../../models/user'; import cc from 'coupon-code'; import { authWithHeaders, authWithUrl, -} from '../../../middlewares/api-v3/auth'; +} from '../../../middlewares/auth'; const stripe = stripeModule(nconf.get('STRIPE_API_KEY')); diff --git a/website/server/index.js b/website/server/index.js index ec8e882c99..e41be424a6 100644 --- a/website/server/index.js +++ b/website/server/index.js @@ -14,13 +14,13 @@ require('babel-polyfill'); global.Promise = require('bluebird'); // Initialize configuration BEFORE anything -const setupNconf = require('./libs/api-v3/setupNconf'); +const setupNconf = require('./libs/setupNconf'); setupNconf(); const nconf = require('nconf'); const cluster = require('cluster'); -const logger = require('./libs/api-v3/logger'); +const logger = require('./libs/logger'); const IS_PROD = nconf.get('IS_PROD'); const IS_DEV = nconf.get('IS_DEV'); diff --git a/website/server/libs/api-v3/amazonPayments.js b/website/server/libs/amazonPayments.js similarity index 98% rename from website/server/libs/api-v3/amazonPayments.js rename to website/server/libs/amazonPayments.js index 4d3a3756b8..bcf19417a5 100644 --- a/website/server/libs/api-v3/amazonPayments.js +++ b/website/server/libs/amazonPayments.js @@ -1,6 +1,6 @@ import amazonPayments from 'amazon-payments'; import nconf from 'nconf'; -import common from '../../../../common'; +import common from '../../../common'; import Bluebird from 'bluebird'; import { BadRequest, diff --git a/website/server/libs/api-v3/analyticsService.js b/website/server/libs/analyticsService.js similarity index 98% rename from website/server/libs/api-v3/analyticsService.js rename to website/server/libs/analyticsService.js index f4ab6e6286..664194c916 100644 --- a/website/server/libs/api-v3/analyticsService.js +++ b/website/server/libs/analyticsService.js @@ -7,7 +7,7 @@ import { each, omit, } from 'lodash'; -import { content as Content } from '../../../../common'; +import { content as Content } from '../../../common'; const AMPLIUDE_TOKEN = nconf.get('AMPLITUDE_KEY'); const GA_TOKEN = nconf.get('GA_ID'); diff --git a/website/server/libs/api-v2/analytics.js b/website/server/libs/api-v2/analytics.js deleted file mode 100644 index 6c1ccd3020..0000000000 --- a/website/server/libs/api-v2/analytics.js +++ /dev/null @@ -1,205 +0,0 @@ -require('./i18n'); - -var _ = require('lodash'); -var Content = require('../../../../common').content; -var Amplitude = require('amplitude'); -var googleAnalytics = require('universal-analytics'); - -var ga; -var amplitude; - -var analytics = { - trackPurchase: trackPurchase, - track: track -} - -function init(options) { - if(!options) { throw 'No options provided' } - - amplitude = new Amplitude(options.amplitudeToken); - ga = googleAnalytics(options.googleAnalytics); - - return analytics; -} - -function track(eventType, data) { - _sendDataToAmplitude(eventType, data); - _sendDataToGoogle(eventType, data); -} - -function _sendDataToAmplitude(eventType, data) { - var amplitudeData = _formatDataForAmplitude(data); - amplitudeData.event_type = eventType; - amplitude.track(amplitudeData).catch(function(error) { - // @TODO log error with new relic - }); -} - -function _sendDataToGoogle(eventType, data) { - var eventData = { - ec: data.category, - ea: eventType - } - - var label = _generateLabelForGoogleAnalytics(data); - if(label) { eventData.el = label; } - - var value = _generateValueForGoogleAnalytics(data); - if(value) { eventData.ev = value; } - - ga.event(eventData).send(); -} - -function _generateLabelForGoogleAnalytics(data) { - var label; - var POSSIBLE_LABELS = ['gaLabel', 'itemKey']; - - _(POSSIBLE_LABELS).each(function(key) { - if(data[key]) { - label = data[key]; - return false; // exit _.each early - } - }).value(); - - return label; -} - -function _generateValueForGoogleAnalytics(data) { - var value; - var POSSIBLE_VALUES = ['gaValue', 'gemCost', 'goldCost']; - - _(POSSIBLE_VALUES).each(function(key) { - if(data[key]) { - value = data[key]; - return false; // exit _.each early - } - }).value(); - - return value; -} - -function trackPurchase(data) { - _sendPurchaseDataToAmplitude(data); - _sendPurchaseDataToGoogle(data); -} - -function _sendPurchaseDataToAmplitude(data) { - var amplitudeData = _formatDataForAmplitude(data); - amplitudeData.event_type = 'purchase'; - amplitudeData.revenue = data.purchaseValue; - - amplitude.track(amplitudeData).catch(function(error) { - // @TODO log error with new relic - }); -} - -function _formatDataForAmplitude(data) { - var PROPERTIES_TO_SCRUB = ['uuid', 'user', 'purchaseValue', 'gaLabel', 'gaValue']; - var event_properties = _.omit(data, PROPERTIES_TO_SCRUB); - - var ampData = { - user_id: data.uuid || 'no-user-id-was-provided', - platform: 'server', - event_properties: event_properties - } - - if(data.user) { - ampData.user_properties = _formatUserData(data.user); - } - - var itemName = _lookUpItemName(data.itemKey); - if(itemName) { - event_properties.itemName = itemName; - } - - return ampData; -} - -function _lookUpItemName(itemKey) { - if (!itemKey) return; - - var gear = Content.gear.flat[itemKey]; - var egg = Content.eggs[itemKey]; - var food = Content.food[itemKey]; - var hatchingPotion = Content.hatchingPotions[itemKey]; - var quest = Content.quests[itemKey]; - var spell = Content.special[itemKey]; - - var itemName; - - if (gear) { - itemName = gear.text(); - } else if (egg) { - itemName = egg.text() + ' Egg'; - } else if (food) { - itemName = food.text(); - } else if (hatchingPotion) { - itemName = hatchingPotion.text() + " Hatching Potion"; - } else if (quest) { - itemName = quest.text(); - } else if (spell) { - itemName = spell.text(); - } - - return itemName; -} - -function _formatUserData(user) { - var properties = {}; - - if (user.stats) { - properties.Class = user.stats.class; - properties.Experience = Math.floor(user.stats.exp); - properties.Gold = Math.floor(user.stats.gp); - properties.Health = Math.ceil(user.stats.hp); - properties.Level = user.stats.lvl; - properties.Mana = Math.floor(user.stats.mp); - } - - properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2; - - if (user.habits && user.dailys && user.todos && user.rewards) { - properties["Number Of Tasks"] = { - habits: user.habits.length, - dailys: user.dailys.length, - todos: user.todos.length, - rewards: user.rewards.length - }; - } - - if (user.contributor && user.contributor.level) { - properties.contributorLevel = user.contributor.level; - } - - if (user.purchased && user.purchased.plan.planId) { - properties.subscription = user.purchased.plan.planId; - } - - return properties; -} - -function _sendPurchaseDataToGoogle(data) { - var label = data.paymentMethod; - var type = data.purchaseType; - var price = data.purchaseValue; - var qty = data.quantity; - var sku = data.sku; - var itemKey = data.itemPurchased; - var variation = type; - if(data.gift) variation += ' - Gift'; - - var eventData = { - ec: 'commerce', - ea: type, - el: label, - ev: price - }; - - ga.event(eventData).send(); - - ga.transaction(data.uuid, price) - .item(price, qty, sku, itemKey, variation) - .send(); -} - -module.exports = init; diff --git a/website/server/libs/api-v2/buildManifest.js b/website/server/libs/api-v2/buildManifest.js deleted file mode 100644 index bfbab1421a..0000000000 --- a/website/server/libs/api-v2/buildManifest.js +++ /dev/null @@ -1,59 +0,0 @@ -var fs = require('fs'); -var path = require('path'); -var nconf = require('nconf'); -var _ = require('lodash'); -var manifestFiles = require("../../../client/manifest.json"); - -var IS_PROD = nconf.get('NODE_ENV') === 'production'; -var buildFiles = []; - -var walk = function(folder){ - var res = fs.readdirSync(folder); - - res.forEach(function(fileName){ - var file = folder + '/' + fileName; - if(fs.statSync(file).isDirectory()){ - walk(file); - }else{ - var relFolder = path.relative(path.join(__dirname, "/../../../build"), folder); - var old = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1'); - - if(relFolder){ - old = relFolder + '/' + old; - fileName = relFolder + '/' + fileName; - } - - buildFiles[old] = fileName; - } - }); -}; - -walk(path.join(__dirname, "/../../../build")); - -var getBuildUrl = module.exports.getBuildUrl = function(url){ - if(buildFiles[url]) return '/' + buildFiles[url]; - - return '/' + url; -}; - -module.exports.getManifestFiles = function(page){ - var files = manifestFiles[page]; - - if(!files) throw new Error("Page not found!"); - - var code = ''; - - if(IS_PROD){ - code += ''; - code += ''; - }else{ - _.each(files.css, function(file){ - code += ''; - }); - _.each(files.js, function(file){ - code += ''; - }); - } - - return code; -}; diff --git a/website/server/libs/api-v2/i18n.js b/website/server/libs/api-v2/i18n.js deleted file mode 100644 index e8295deb03..0000000000 --- a/website/server/libs/api-v2/i18n.js +++ /dev/null @@ -1,180 +0,0 @@ -var fs = require('fs'), - path = require('path'), - _ = require('lodash'), - User = require('../../models/user').model, - accepts = require('accepts'), - shared = require('../../../../common'), - translations = {}; - -var localePath = path.join(__dirname, "/../../../../common/locales/") - -var loadTranslations = function(locale){ - var files = fs.readdirSync(path.join(localePath, locale)); - translations[locale] = {}; - _.each(files, function(file){ - if(path.extname(file) !== '.json') return; - _.merge(translations[locale], require(path.join(localePath, locale, file))); - }); -}; - -// First fetch english so we can merge with missing strings in other languages -loadTranslations('en'); - -fs.readdirSync(localePath).forEach(function(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); -}); - -var langCodes = Object.keys(translations); - -var avalaibleLanguages = _.map(langCodes, function(langCode){ - return { - code: langCode, - name: translations[langCode].languageName - } -}); - -// Load MomentJS localization files -var momentLangs = {}; - -// Handle different language codes from MomentJS and /locales -var momentLangsMapping = { - 'en': 'en-gb', - 'en_GB': 'en-gb', - 'no': 'nn', - 'zh': 'zh-cn', - 'es_419': 'es' -}; - -var momentLangs = {}; - -_.each(langCodes, function(code){ - var lang = _.find(avalaibleLanguages, {code: 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 - var f = fs.readFileSync(path.join(__dirname, '/../../node_modules/moment/locale/' + lang.momentLangCode + '.js'), 'utf8'); - momentLangs[code] = f; - }catch (e){} -}); - -// Remove en_GB from langCodes checked by browser to avaoi it being -// used in place of plain original 'en' -var defaultLangCodes = _.without(langCodes, 'en_GB'); - -// A list of languages that have different versions -var multipleVersionsLanguages = ['es', 'zh']; - -var latinAmericanSpanishes = { - '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', -}; - -var chineseVersions = { - 'zh-tw': 'zh_TW', -}; - -var getUserLanguage = function(req, res, next){ - var getFromBrowser = function(){ - var acceptedLanguages = accepts(req).languages(); - - var acceptable = _(acceptedLanguages).map(function(lang){ - return lang.slice(0, 2); - }).uniq().value(); - - var matches = _.intersection(acceptable, defaultLangCodes); - - var iAcceptedCompleteLang = (matches.length > 0) ? multipleVersionsLanguages.indexOf(matches[0].toLowerCase()) : -1; - - if(iAcceptedCompleteLang !== -1){ - var acceptedCompleteLang = _.find(acceptedLanguages, function(accepted){ - return accepted.slice(0, 2) == multipleVersionsLanguages[iAcceptedCompleteLang]; - }); - - if(acceptedCompleteLang){ - acceptedCompleteLang = acceptedCompleteLang.toLowerCase(); - }else{ - return 'en'; - } - - if(matches[0] === 'es'){ - return latinAmericanSpanishes[acceptedCompleteLang] || 'es'; - }else if(matches[0] === 'zh'){ - return chineseVersions[acceptedCompleteLang] || 'zh'; - }else{ - return en; - } - - }else if(matches.length > 0){ - return matches[0].toLowerCase(); - }else{ - return 'en'; - } - }; - - var getFromUser = function(user){ - var lang; - if(user && user.preferences.language && translations[user.preferences.language]){ - lang = user.preferences.language; - }else{ - var preferred = getFromBrowser(); - lang = translations[preferred] ? preferred : 'en'; - } - req.language = lang; - next(); - }; - - if(req.query.lang){ - req.language = translations[req.query.lang] ? (req.query.lang) : 'en'; - next(); - }else if(req.locals && req.locals.user){ - getFromUser(req.locals.user); - }else if(req.session && req.session.userId){ - User.findOne({_id: req.session.userId}, 'preferences.language', function(err, user){ - if(err) return next(err); - getFromUser(user); - }); - }else{ - getFromUser(null); - } -}; - -shared.i18n.translations = translations; - -module.exports = { - translations: translations, - avalaibleLanguages: avalaibleLanguages, - langCodes: langCodes, - getUserLanguage: getUserLanguage, - momentLangs: momentLangs -}; - - -// Export en strings only, temporary solution for mobile -// This is copied from middlewares/locals#t() -module.exports.enTranslations = function(){ // stringName and vars are the allowed parameters - var language = _.find(avalaibleLanguages, {code: 'en'}); - //language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined); - var args = Array.prototype.slice.call(arguments, 0); - args.push(language.code); - return shared.i18n.t.apply(null, args); -}; diff --git a/website/server/libs/api-v2/logging.js b/website/server/libs/api-v2/logging.js deleted file mode 100644 index 454b5a5aa8..0000000000 --- a/website/server/libs/api-v2/logging.js +++ /dev/null @@ -1,35 +0,0 @@ -var nconf = require('nconf'); -var winston = require('winston'); - -var logger; - -if (!logger) { - logger = new (winston.Logger)({}); - logger.add(winston.transports.Console, {colorize:true}); // TODO remove - - if (nconf.get('NODE_ENV') !== 'production') { - logger.add(winston.transports.File, {filename: 'habitrpg.log'}); - } -} - -// A custom log function that wraps Winston. Makes it easy to instrument code -// and still possible to replace Winston in the future. -module.exports.log = function(/* variable args */) { - if (logger) - logger.log.apply(logger, arguments); -}; - -module.exports.info = function(/* variable args */) { - if (logger) - logger.info.apply(logger, arguments); -}; - -module.exports.warn = function(/* variable args */) { - if (logger) - logger.warn.apply(logger, arguments); -}; - -module.exports.error = function(/* variable args */) { - if (logger) - logger.error.apply(logger, arguments); -}; \ No newline at end of file diff --git a/website/server/libs/api-v2/utils.js b/website/server/libs/api-v2/utils.js deleted file mode 100644 index 8657bf6513..0000000000 --- a/website/server/libs/api-v2/utils.js +++ /dev/null @@ -1,204 +0,0 @@ -var nodemailer = require('nodemailer'); -var nconf = require('nconf'); -var crypto = require('crypto'); -var path = require("path"); -var request = require('request'); - -const IS_PROD = nconf.get('IS_PROD'); -const BASE_URL = nconf.get('BASE_URL'); - -module.exports.sendEmail = function(mailData) { - var smtpTransport = nodemailer.createTransport({ - service: nconf.get('SMTP_SERVICE'), - auth: { - user: nconf.get('SMTP_USER'), - pass: nconf.get('SMTP_PASS') - } - }); - - smtpTransport.sendMail(mailData, function(error, response){ - var logging = require('./api-v2/logging'); - if(error) logging.error(error); - else logging.info("Message sent: " + response.message); - smtpTransport.close(); // shut down the connection pool, no more messages - }); -} - -function getUserInfo(user, fields) { - var info = {}; - - if(fields.indexOf('name') != -1){ - if(user.auth.local){ - info.name = user.profile.name || user.auth.local.username; - }else if(user.auth.facebook){ - info.name = user.profile.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){ - info.canSend = user.preferences.emailNotifications.unsubscribeFromAll !== true; - } - - return info; -} - -module.exports.getUserInfo = getUserInfo; - -module.exports.txnEmail = function(mailingInfoArray, emailType, variables, personalVariables){ - var mailingInfoArray = Array.isArray(mailingInfoArray) ? mailingInfoArray : [mailingInfoArray]; - - var 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(function(mailingInfo){ - return mailingInfo._id ? getUserInfo(mailingInfo, ['_id', 'email', 'name', 'canSend']) : mailingInfo; - }).filter(function(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(function(mailingInfo){ - return { - rcpt: mailingInfo.email, - vars: [ - { - name: 'RECIPIENT_NAME', - content: mailingInfo.name - }, - { - name: 'RECIPIENT_UNSUB_URL', - content: '/unsubscribe?code=' + module.exports.encrypt(JSON.stringify({ - _id: mailingInfo._id, - email: mailingInfo.email - })) - } - ] - } - }); - }else{ - var temporaryPersonalVariables = {}; - - mailingInfoArray.forEach(function(mailingInfo){ - temporaryPersonalVariables[mailingInfo.email] = { - name: mailingInfo.name, - _id: mailingInfo._id - } - }); - - personalVariables.forEach(function(singlePersonalVariables){ - singlePersonalVariables.vars.push( - { - name: 'RECIPIENT_NAME', - content: temporaryPersonalVariables[singlePersonalVariables.rcpt].name - }, - { - name: 'RECIPIENT_UNSUB_URL', - content: '/unsubscribe?code=' + module.exports.encrypt(JSON.stringify({ - _id: temporaryPersonalVariables[singlePersonalVariables.rcpt]._id, - email: singlePersonalVariables.rcpt - })) - } - ) - }); - } - - if(IS_PROD && mailingInfoArray.length > 0){ - request({ - url: nconf.get('EMAIL_SERVER:url') + '/job', - method: 'POST', - auth: { - user: nconf.get('EMAIL_SERVER:authUser'), - pass: nconf.get('EMAIL_SERVER:authPassword') - }, - json: { - type: 'email', - data: { - emailType: emailType, - to: mailingInfoArray, - variables: variables, - personalVariables: personalVariables - }, - options: { - priority: 'high', - attempts: 5, - backoff: {delay: 10*60*1000, type: 'fixed'} - } - } - }); - } -} - -// Encryption using http://dailyjs.com/2010/12/06/node-tutorial-5/ -// Note: would use [password-hash](https://github.com/davidwood/node-password-hash), but we need to run -// model.query().equals(), so it's a PITA to work in their verify() function - -module.exports.encryptPassword = function(password, salt) { - return crypto.createHmac('sha1', salt).update(password).digest('hex'); -} - -module.exports.makeSalt = function() { - var len = 10; - return crypto.randomBytes(Math.ceil(len / 2)).toString('hex').substring(0, len); -} - -// Prepare to export analytics object -// Export emoty methods until the right ones are ready -module.exports.analytics = { track: function() { }, trackPurchase: function() { } }; - -/** - * Load nconf and define default configuration values if config.json or ENV vars are not found - */ -module.exports.setupConfig = function(){ - if (nconf.get('IS_DEV')) - Error.stackTraceLimit = Infinity; - if (IS_PROD && nconf.get('NEW_RELIC_ENABLED') === 'true') - require('newrelic'); - - var analytics = IS_PROD && require('./api-v2/analytics'); - var analyticsTokens = { - amplitudeToken: nconf.get('AMPLITUDE_KEY'), - googleAnalytics: nconf.get('GA_ID') - } - - if(analytics){ - analytics = analytics(analyticsTokens); - // Use the right analytics methods, don't substitute the entire object - // or all the require() across the code will keep the empty methods - module.exports.analytics.track = analytics.track; - module.exports.analytics.trackPurchase = analytics.trackPurchase; - } -}; - -var algorithm = 'aes-256-ctr'; -module.exports.encrypt = function(text){ - var cipher = crypto.createCipher(algorithm,nconf.get('SESSION_SECRET')) - var crypted = cipher.update(text,'utf8','hex') - crypted += cipher.final('hex'); - return crypted; -} - -module.exports.decrypt = function(text){ - var decipher = crypto.createDecipher(algorithm,nconf.get('SESSION_SECRET')) - var dec = decipher.update(text,'hex','utf8') - dec += decipher.final('utf8'); - return dec; -} diff --git a/website/server/libs/api-v2/webhook.js b/website/server/libs/api-v2/webhook.js deleted file mode 100644 index 847c9e08b3..0000000000 --- a/website/server/libs/api-v2/webhook.js +++ /dev/null @@ -1,24 +0,0 @@ -var _ = require('lodash'); -var request = require('request'); -var validator = require('validator'); - -function sendTaskWebhook(webhooks, data) { - _.each(webhooks, function(hook){ - if (!hook.enabled || !validator.isURL(hook.url)) return; - - request.post({ - url: hook.url, - body: { - direction: data.task.direction, - task: data.task.details, - delta: data.task.delta, - user: data.user - }, - json: true - }); - }); -} - -module.exports = { - sendTaskWebhook: sendTaskWebhook -}; diff --git a/website/server/libs/api-v3/aws.js b/website/server/libs/aws.js similarity index 100% rename from website/server/libs/api-v3/aws.js rename to website/server/libs/aws.js diff --git a/website/server/libs/api-v3/baseModel.js b/website/server/libs/baseModel.js similarity index 100% rename from website/server/libs/api-v3/baseModel.js rename to website/server/libs/baseModel.js diff --git a/website/server/libs/api-v3/buildManifest.js b/website/server/libs/buildManifest.js similarity index 92% rename from website/server/libs/api-v3/buildManifest.js rename to website/server/libs/buildManifest.js index 2945ef151b..809f5fd724 100644 --- a/website/server/libs/api-v3/buildManifest.js +++ b/website/server/libs/buildManifest.js @@ -2,8 +2,8 @@ import fs from 'fs'; import path from 'path'; import nconf from 'nconf'; -const MANIFEST_FILE_PATH = path.join(__dirname, '/../../../client/manifest.json'); -const BUILD_FOLDER_PATH = path.join(__dirname, '/../../../build'); +const MANIFEST_FILE_PATH = path.join(__dirname, '/../../client/manifest.json'); +const BUILD_FOLDER_PATH = path.join(__dirname, '/../../build'); let manifestFiles = require(MANIFEST_FILE_PATH); const IS_PROD = nconf.get('IS_PROD'); diff --git a/website/server/libs/api-v3/collectionManipulators.js b/website/server/libs/collectionManipulators.js similarity index 100% rename from website/server/libs/api-v3/collectionManipulators.js rename to website/server/libs/collectionManipulators.js diff --git a/website/server/libs/api-v3/cron.js b/website/server/libs/cron.js similarity index 98% rename from website/server/libs/api-v3/cron.js rename to website/server/libs/cron.js index de65168fea..814ffe9a07 100644 --- a/website/server/libs/api-v3/cron.js +++ b/website/server/libs/cron.js @@ -1,8 +1,8 @@ import moment from 'moment'; import Bluebird from 'bluebird'; -import { model as User } from '../../models/user'; -import common from '../../../../common/'; -import { preenUserHistory } from '../../libs/api-v3/preening'; +import { model as User } from '../models/user'; +import common from '../../../common/'; +import { preenUserHistory } from '../libs/preening'; import _ from 'lodash'; import nconf from 'nconf'; diff --git a/website/server/libs/api-v3/csvStringify.js b/website/server/libs/csvStringify.js similarity index 100% rename from website/server/libs/api-v3/csvStringify.js rename to website/server/libs/csvStringify.js diff --git a/website/server/libs/api-v3/email.js b/website/server/libs/email.js similarity index 99% rename from website/server/libs/api-v3/email.js rename to website/server/libs/email.js index 26bd08f105..1e1fc79383 100644 --- a/website/server/libs/api-v3/email.js +++ b/website/server/libs/email.js @@ -1,6 +1,6 @@ import { createTransport } from 'nodemailer'; import nconf from 'nconf'; -import { TAVERN_ID } from '../../models/group'; +import { TAVERN_ID } from '../models/group'; import { encrypt } from './encryption'; import request from 'request'; import logger from './logger'; diff --git a/website/server/libs/api-v3/encryption.js b/website/server/libs/encryption.js similarity index 100% rename from website/server/libs/api-v3/encryption.js rename to website/server/libs/encryption.js diff --git a/website/server/libs/api-v3/errors.js b/website/server/libs/errors.js similarity index 97% rename from website/server/libs/api-v3/errors.js rename to website/server/libs/errors.js index 2b6d52bbe3..73a3c23c54 100644 --- a/website/server/libs/api-v3/errors.js +++ b/website/server/libs/errors.js @@ -1,4 +1,4 @@ -import common from '../../../../common'; +import common from '../../../common'; export const CustomError = common.errors.CustomError; diff --git a/website/server/libs/api-v3/i18n.js b/website/server/libs/i18n.js similarity index 96% rename from website/server/libs/api-v3/i18n.js rename to website/server/libs/i18n.js index 4c424ace39..0e71793bc4 100644 --- a/website/server/libs/api-v3/i18n.js +++ b/website/server/libs/i18n.js @@ -1,9 +1,9 @@ import fs from 'fs'; import path from 'path'; import _ from 'lodash'; -import shared from '../../../../common'; +import shared from '../../../common'; -export const localePath = path.join(__dirname, '/../../../../common/locales/'); +export const localePath = path.join(__dirname, '/../../../common/locales/'); // Store translations export let translations = {}; diff --git a/website/server/libs/api-v3/inAppPurchases.js b/website/server/libs/inAppPurchases.js similarity index 100% rename from website/server/libs/api-v3/inAppPurchases.js rename to website/server/libs/inAppPurchases.js diff --git a/website/server/libs/api-v3/logger.js b/website/server/libs/logger.js similarity index 100% rename from website/server/libs/api-v3/logger.js rename to website/server/libs/logger.js diff --git a/website/server/libs/api-v3/password.js b/website/server/libs/password.js similarity index 100% rename from website/server/libs/api-v3/password.js rename to website/server/libs/password.js diff --git a/website/server/libs/api-v3/payments.js b/website/server/libs/payments.js similarity index 99% rename from website/server/libs/api-v3/payments.js rename to website/server/libs/payments.js index 66902645d9..4ae9fcb72d 100644 --- a/website/server/libs/api-v3/payments.js +++ b/website/server/libs/payments.js @@ -6,7 +6,7 @@ import { } from './email'; import moment from 'moment'; import sendPushNotification from './pushNotifications'; -import shared from '../../../../common' ; +import shared from '../../../common' ; let api = {}; diff --git a/website/server/libs/api-v3/preening.js b/website/server/libs/preening.js similarity index 100% rename from website/server/libs/api-v3/preening.js rename to website/server/libs/preening.js diff --git a/website/server/libs/api-v3/pushNotifications.js b/website/server/libs/pushNotifications.js similarity index 100% rename from website/server/libs/api-v3/pushNotifications.js rename to website/server/libs/pushNotifications.js diff --git a/website/server/libs/api-v3/pusher.js b/website/server/libs/pusher.js similarity index 100% rename from website/server/libs/api-v3/pusher.js rename to website/server/libs/pusher.js diff --git a/website/server/libs/api-v3/routes.js b/website/server/libs/routes.js similarity index 95% rename from website/server/libs/api-v3/routes.js rename to website/server/libs/routes.js index c0399fb73c..46b6478823 100644 --- a/website/server/libs/api-v3/routes.js +++ b/website/server/libs/routes.js @@ -2,8 +2,8 @@ import fs from 'fs'; import _ from 'lodash'; import { getUserLanguage, -} from '../../middlewares/api-v3/language'; -import cron from '../../middlewares/api-v3/cron'; +} from '../middlewares/language'; +import cron from '../middlewares/cron'; // Wrapper function to handler `async` route handlers that return promises // It takes the async function, execute it and pass any error to next (args[2]) diff --git a/website/server/libs/api-v3/setupMongoose.js b/website/server/libs/setupMongoose.js similarity index 100% rename from website/server/libs/api-v3/setupMongoose.js rename to website/server/libs/setupMongoose.js diff --git a/website/server/libs/api-v3/setupNconf.js b/website/server/libs/setupNconf.js similarity index 84% rename from website/server/libs/api-v3/setupNconf.js rename to website/server/libs/setupNconf.js index d88b04014e..51c643cd20 100644 --- a/website/server/libs/api-v3/setupNconf.js +++ b/website/server/libs/setupNconf.js @@ -1,7 +1,7 @@ import nconf from 'nconf'; import { join, resolve } from 'path'; -const PATH_TO_CONFIG = join(resolve(__dirname, '../../../../config.json')); +const PATH_TO_CONFIG = join(resolve(__dirname, '../../../config.json')); module.exports = function setupNconf (file) { let configFile = file || PATH_TO_CONFIG; diff --git a/website/server/libs/api-v3/setupPassport.js b/website/server/libs/setupPassport.js similarity index 100% rename from website/server/libs/api-v3/setupPassport.js rename to website/server/libs/setupPassport.js diff --git a/website/server/libs/api-v3/webhook.js b/website/server/libs/webhook.js similarity index 100% rename from website/server/libs/api-v3/webhook.js rename to website/server/libs/webhook.js diff --git a/website/server/middlewares/api-v3/analytics.js b/website/server/middlewares/analytics.js similarity index 87% rename from website/server/middlewares/api-v3/analytics.js rename to website/server/middlewares/analytics.js index 8512d16011..f86becb773 100644 --- a/website/server/middlewares/api-v3/analytics.js +++ b/website/server/middlewares/analytics.js @@ -3,7 +3,7 @@ import { track, trackPurchase, mockAnalyticsService, -} from '../../libs/api-v3/analyticsService'; +} from '../libs/analyticsService'; let service; diff --git a/website/server/middlewares/api-v2/domain.js b/website/server/middlewares/api-v2/domain.js deleted file mode 100644 index db3e65df2e..0000000000 --- a/website/server/middlewares/api-v2/domain.js +++ /dev/null @@ -1,49 +0,0 @@ -var nconf = require('nconf'); -var moment = require('moment'); -var domainMiddleware = require('domain-middleware'); -var os = require('os'); -var request = require('request'); - -var IS_PROD = nconf.get('NODE_ENV') === 'production'; - -module.exports = function(server,mongoose) { - /* if (IS_PROD) { - var mins = 3, // how often to run this check - useAvg = false, // use average over 3 minutes, or simply the last minute's report - url = 'https://api.newrelic.com/v2/applications/'+nconf.get('NEW_RELIC_APPLICATION_ID')+'/metrics/data.json?names[]=Apdex&values[]=score'; - setInterval(function(){ - // see https://docs.newrelic.com/docs/apm/apis/api-v2-examples/average-response-time-examples-api-v2, https://rpm.newrelic.com/api/explore/applications/data - request({ - url: useAvg ? url+'&from='+moment().subtract({minutes:mins}).utc().format()+'&to='+moment().utc().format()+'&summarize=true' : url, - headers: {'X-Api-Key': nconf.get('NEW_RELIC_API_KEY')} - }, function(err, response, body){ - var ts = JSON.parse(body).metric_data.metrics[0].timeslices, - score = ts[ts.length-1].values.score, - memory = os.freemem() / os.totalmem(), - memoryHigh = memory < 0.1; - - if (memoryHigh) { - var newRelicMemoryLeakMessage = '[Memory Leak] Apdex='+score+' Memory='+parseFloat(memory).toFixed(3)+' Time='+moment().format(); - throw newRelicMemoryLeakMessage; - } - }); - - var memory = os.freemem() / os.totalmem(), - memoryHigh = memory < 0.1; - if (memoryHigh) { - var memoryLeakMessage = '[Memory Leak] Memory='+parseFloat(memory).toFixed(3)+' Time='+moment().format(); - throw memoryLeakMessage; - } - }, mins*60*1000); - } */ - - return domainMiddleware({ - server: { - close:function(){ - server.close(); - mongoose.connection.close(); - } - }, - killTimeout: 10000 - }); -}; diff --git a/website/server/middlewares/api-v2/errorHandler.js b/website/server/middlewares/api-v2/errorHandler.js deleted file mode 100644 index fcea9d175c..0000000000 --- a/website/server/middlewares/api-v2/errorHandler.js +++ /dev/null @@ -1,17 +0,0 @@ -var logging = require('../../libs/api-v2/logging'); - -module.exports = function(err, req, res, next) { - //res.locals.domain.emit('error', err); - // when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER) - var stack = (err.stack ? err.stack : err.message ? err.message : err) + - "\n ----------------------------\n" + - "\n\noriginalUrl: " + req.originalUrl + - "\n\nauth: " + req.headers['x-api-user'] + ' | ' + req.headers['x-api-key'] + - "\n\nheaders: " + JSON.stringify(req.headers) + - "\n\nbody: " + JSON.stringify(req.body) + - (res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : ""); - logging.error(stack); - var message = err.message ? err.message : err; - message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length); - res.status(500).json({err:message}); //res.end(err.message); -}; diff --git a/website/server/middlewares/api-v2/locals.js b/website/server/middlewares/api-v2/locals.js deleted file mode 100644 index 223186a81a..0000000000 --- a/website/server/middlewares/api-v2/locals.js +++ /dev/null @@ -1,70 +0,0 @@ -var nconf = require('nconf'); -var _ = require('lodash'); -var utils = require('../libs/api-v2/utils'); -var shared = require('../../../common'); -var i18n = require('../libs/api-v2/i18n'); -var buildManifest = require('../libs/api-v2/buildManifest'); -var shared = require('../../../common'); -var forceRefresh = require('./forceRefresh'); -var tavernQuest = require('../models/group').tavernQuest; -var mods = require('../models/user').mods; - -// To avoid stringifying more data then we need, -// items from `env` used on the client will have to be specified in this array -var clientVars = ['language', 'isStaticPage', 'avalaibleLanguages', 'translations', - 'FACEBOOK_KEY', 'NODE_ENV', 'BASE_URL', 'GA_ID', - 'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY', - 'worldDmg', 'mods', 'IS_MOBILE']; - -var env = { - getManifestFiles: buildManifest.getManifestFiles, - getBuildUrl: buildManifest.getBuildUrl, - _: _, - clientVars: clientVars, - mods: mods, - Content: shared.content, - siteVersion: forceRefresh.siteVersion, - avalaibleLanguages: i18n.avalaibleLanguages, - AMAZON_PAYMENTS: { - SELLER_ID: nconf.get('AMAZON_PAYMENTS:SELLER_ID'), - CLIENT_ID: nconf.get('AMAZON_PAYMENTS:CLIENT_ID') - } -}; - -'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY AMPLITUDE_KEY'.split(' ').forEach(function(key){ - env[key] = nconf.get(key); -}); - -module.exports = function(req, res, next) { - var language = _.find(i18n.avalaibleLanguages, {code: req.language}); - var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/' - - // Load moment.js language file only when not on static pages - language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined); - - res.locals.habitrpg = _.assign(env, { - IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')), - language: language, - isStaticPage: isStaticPage, - translations: i18n.translations[language.code], - t: function(){ // stringName and vars are the allowed parameters - var args = Array.prototype.slice.call(arguments, 0); - args.push(language.code); - return shared.i18n.t.apply(null, args); - }, - // Defined here and not outside of the middleware because tavernQuest might be an - // empty object until the query to fetch it finishes - worldDmg: (tavernQuest && tavernQuest.extra && tavernQuest.extra.worldDmg) || {}, - }); - - // Put query-string party (& guild but use partyInvite for backward compatibility) - // invitations into session to be handled later - // TODO once we have proper logging in place, log errors here - if(req.query.partyInvite){ - try{ - req.session.partyInvite = JSON.parse(utils.decrypt(req.query.partyInvite)); - } catch(e){} - } - - next(); -}; diff --git a/website/server/middlewares/api-v3/v2.js b/website/server/middlewares/api-v3/v2.js deleted file mode 100644 index ec49e326a4..0000000000 --- a/website/server/middlewares/api-v3/v2.js +++ /dev/null @@ -1,27 +0,0 @@ -// DEPRECATED BUT STILL ACTIVE - -// import path from 'path'; -import swagger from 'swagger-node-express'; -// import shared from '../../../../common'; -import express from 'express'; -import analytics from './analytics'; -import responseHandler from './response'; - -const v2app = express(); - -// re-set the view options because they are not inherited from the top level app -v2app.set('view engine', 'jade'); -v2app.set('views', `${__dirname}/../../../views`); - -v2app.use(analytics); -v2app.use(responseHandler); - - -// Custom Directives -v2app.use('/', require('../../routes/api-v2/auth')); - -require('../../routes/api-v2/swagger')(swagger, v2app); - -v2app.use(require('../api-v2/errorHandler')); - -module.exports = v2app; diff --git a/website/server/middlewares/apiThrottle.js b/website/server/middlewares/apiThrottle.js deleted file mode 100644 index b392cc777e..0000000000 --- a/website/server/middlewares/apiThrottle.js +++ /dev/null @@ -1,26 +0,0 @@ -var nconf = require('nconf'); -var limiter = require('connect-ratelimit'); - -var IS_PROD = nconf.get('NODE_ENV') === 'production'; - -// TODO since Habitica runs on many different servers this module is pretty useless -// as it will only block requests that go to the same server but anyway we should probably have a rate limiter in place - -module.exports = function(app) { - // disable the rate limiter middleware - if (/*!IS_PROD || */true) return; - app.use(limiter({ - end:false, - categories:{ - normal: { - // 2 req/s, but split as minutes - totalRequests: 80, - every: 60000 - } - } - })).use(function(req,res,next){ - //logging.info(res.ratelimit); - if (res.ratelimit.exceeded) return res.status(429).json({err:'Rate limit exceeded'}); - next(); - }); -}; diff --git a/website/server/middlewares/api-v3/auth.js b/website/server/middlewares/auth.js similarity index 97% rename from website/server/middlewares/api-v3/auth.js rename to website/server/middlewares/auth.js index 0feb73a98d..136159132a 100644 --- a/website/server/middlewares/api-v3/auth.js +++ b/website/server/middlewares/auth.js @@ -1,9 +1,9 @@ import { NotAuthorized, -} from '../../libs/api-v3/errors'; +} from '../libs/errors'; import { model as User, -} from '../../models/user'; +} from '../models/user'; // Strins won't be translated here because getUserLanguage has not run yet diff --git a/website/server/middlewares/api-v3/cors.js b/website/server/middlewares/cors.js similarity index 100% rename from website/server/middlewares/api-v3/cors.js rename to website/server/middlewares/cors.js diff --git a/website/server/middlewares/api-v3/cron.js b/website/server/middlewares/cron.js similarity index 97% rename from website/server/middlewares/api-v3/cron.js rename to website/server/middlewares/cron.js index e4745f734e..9ab349a4b5 100644 --- a/website/server/middlewares/api-v3/cron.js +++ b/website/server/middlewares/cron.js @@ -1,11 +1,11 @@ import _ from 'lodash'; import moment from 'moment'; -import common from '../../../../common'; -import * as Tasks from '../../models/task'; +import common from '../../../common'; +import * as Tasks from '../models/task'; import Bluebird from 'bluebird'; -import { model as Group } from '../../models/group'; -import { model as User } from '../../models/user'; -import { recoverCron, cron } from '../../libs/api-v3/cron'; +import { model as Group } from '../models/group'; +import { model as User } from '../models/user'; +import { recoverCron, cron } from '../libs/cron'; import { v4 as uuid } from 'uuid'; const daysSince = common.daysSince; diff --git a/website/server/middlewares/api-v3/domain.js b/website/server/middlewares/domain.js similarity index 100% rename from website/server/middlewares/api-v3/domain.js rename to website/server/middlewares/domain.js diff --git a/website/server/middlewares/api-v3/ensureAccessRight.js b/website/server/middlewares/ensureAccessRight.js similarity index 91% rename from website/server/middlewares/api-v3/ensureAccessRight.js rename to website/server/middlewares/ensureAccessRight.js index 2fb64ed8af..c05aedb3ff 100644 --- a/website/server/middlewares/api-v3/ensureAccessRight.js +++ b/website/server/middlewares/ensureAccessRight.js @@ -1,6 +1,6 @@ import { NotAuthorized, -} from '../../libs/api-v3/errors'; +} from '../libs/errors'; export function ensureAdmin (req, res, next) { let user = res.locals.user; diff --git a/website/server/middlewares/api-v3/ensureDevelpmentMode.js b/website/server/middlewares/ensureDevelpmentMode.js similarity index 85% rename from website/server/middlewares/api-v3/ensureDevelpmentMode.js rename to website/server/middlewares/ensureDevelpmentMode.js index 98f70d33f5..c15dde0ea0 100644 --- a/website/server/middlewares/api-v3/ensureDevelpmentMode.js +++ b/website/server/middlewares/ensureDevelpmentMode.js @@ -1,7 +1,7 @@ import nconf from 'nconf'; import { NotFound, -} from '../../libs/api-v3/errors'; +} from '../libs/errors'; module.exports = function ensureDevelpmentMode (req, res, next) { if (nconf.get('IS_PROD')) { diff --git a/website/server/middlewares/api-v3/errorHandler.js b/website/server/middlewares/errorHandler.js similarity index 97% rename from website/server/middlewares/api-v3/errorHandler.js rename to website/server/middlewares/errorHandler.js index b71fb9666e..a42251fd7d 100644 --- a/website/server/middlewares/api-v3/errorHandler.js +++ b/website/server/middlewares/errorHandler.js @@ -1,11 +1,11 @@ // The error handler middleware that handles all errors // and respond to the client -import logger from '../../libs/api-v3/logger'; +import logger from '../libs/logger'; import { CustomError, BadRequest, InternalServerError, -} from '../../libs/api-v3/errors'; +} from '../libs/errors'; import { map, omit, diff --git a/website/server/middlewares/forceRefresh.js b/website/server/middlewares/forceRefresh.js deleted file mode 100644 index f843694790..0000000000 --- a/website/server/middlewares/forceRefresh.js +++ /dev/null @@ -1,11 +0,0 @@ -// TODO do we need this module anymore in v3? No - -module.exports.siteVersion = 1; - -module.exports.middleware = function(req, res, next){ - if(req.query.siteVersion && req.query.siteVersion != module.exports.siteVersion){ - return res.status(400).json({needRefresh: true}); - } - - return next(); -}; diff --git a/website/server/middlewares/api-v3/index.js b/website/server/middlewares/index.js similarity index 95% rename from website/server/middlewares/api-v3/index.js rename to website/server/middlewares/index.js index 694fdb6ea5..2283350677 100644 --- a/website/server/middlewares/api-v3/index.js +++ b/website/server/middlewares/index.js @@ -29,14 +29,14 @@ import { const IS_PROD = nconf.get('IS_PROD'); const DISABLE_LOGGING = nconf.get('DISABLE_REQUEST_LOGGING'); -const PUBLIC_DIR = path.join(__dirname, '/../../../client'); +const PUBLIC_DIR = path.join(__dirname, '/../../client'); const SESSION_SECRET = nconf.get('SESSION_SECRET'); const TWO_WEEKS = 1000 * 60 * 60 * 24 * 14; module.exports = function attachMiddlewares (app, server) { app.set('view engine', 'jade'); - app.set('views', `${__dirname}/../views`); + app.set('views', `${__dirname}/../../views`); app.use(domainMiddleware(server, mongoose)); diff --git a/website/server/middlewares/api-v3/language.js b/website/server/middlewares/language.js similarity index 95% rename from website/server/middlewares/api-v3/language.js rename to website/server/middlewares/language.js index b83c9d5208..9223c53bad 100644 --- a/website/server/middlewares/api-v3/language.js +++ b/website/server/middlewares/language.js @@ -1,12 +1,12 @@ -import { model as User } from '../../models/user'; +import { model as User } from '../models/user'; import accepts from 'accepts'; -import common from '../../../../common'; +import common from '../../../common'; import _ from 'lodash'; import { translations, defaultLangCodes, multipleVersionsLanguages, -} from '../../libs/api-v3/i18n'; +} from '../libs/i18n'; const i18n = common.i18n; diff --git a/website/server/middlewares/api-v3/locals.js b/website/server/middlewares/locals.js similarity index 86% rename from website/server/middlewares/api-v3/locals.js rename to website/server/middlewares/locals.js index 306e4b26c7..17a945f1fd 100644 --- a/website/server/middlewares/api-v3/locals.js +++ b/website/server/middlewares/locals.js @@ -1,14 +1,13 @@ import nconf from 'nconf'; import _ from 'lodash'; -import shared from '../../../../common'; -import * as i18n from '../../libs/api-v3/i18n'; +import shared from '../../../common'; +import * as i18n from '../libs/i18n'; import { getBuildUrl, getManifestFiles, -} from '../../libs/api-v3/buildManifest'; -import forceRefresh from './../forceRefresh'; -import { tavernQuest } from '../../models/group'; -import { mods } from '../../models/user'; +} from '../libs/buildManifest'; +import { tavernQuest } from '../models/group'; +import { mods } from '../models/user'; // To avoid stringifying more data then we need, // items from `env` used on the client will have to be specified in this array @@ -24,7 +23,6 @@ let env = { clientVars: CLIENT_VARS, mods, Content: shared.content, - siteVersion: forceRefresh.siteVersion, availableLanguages: i18n.availableLanguages, AMAZON_PAYMENTS: { SELLER_ID: nconf.get('AMAZON_PAYMENTS:SELLER_ID'), diff --git a/website/server/middlewares/api-v3/maintenanceMode.js b/website/server/middlewares/maintenanceMode.js similarity index 81% rename from website/server/middlewares/api-v3/maintenanceMode.js rename to website/server/middlewares/maintenanceMode.js index 331663125a..d6f314a02e 100644 --- a/website/server/middlewares/api-v3/maintenanceMode.js +++ b/website/server/middlewares/maintenanceMode.js @@ -17,9 +17,9 @@ module.exports = function maintenanceMode (req, res, next) { if (req.headers && req.headers.accept && req.headers.accept.indexOf('text/html') !== -1) { if (req.path === '/views/static/maintenance-info') { - return res.status(503).render('../../../views/static/maintenance-info', pageVariables); + return res.status(503).render('../../views/static/maintenance-info', pageVariables); } else { - return res.status(503).render('../../../views/static/maintenance', pageVariables); + return res.status(503).render('../../views/static/maintenance', pageVariables); } } else { return res.status(503).send({ diff --git a/website/server/middlewares/api-v3/notFound.js b/website/server/middlewares/notFound.js similarity index 76% rename from website/server/middlewares/api-v3/notFound.js rename to website/server/middlewares/notFound.js index 6a71b5def4..8e2814148b 100644 --- a/website/server/middlewares/api-v3/notFound.js +++ b/website/server/middlewares/notFound.js @@ -1,6 +1,6 @@ import { NotFound, -} from '../../libs/api-v3/errors'; +} from '../libs/errors'; module.exports = function NotFoundMiddleware (req, res, next) { next(new NotFound()); diff --git a/website/server/middlewares/api-v3/redirects.js b/website/server/middlewares/redirects.js similarity index 100% rename from website/server/middlewares/api-v3/redirects.js rename to website/server/middlewares/redirects.js diff --git a/website/server/middlewares/api-v3/response.js b/website/server/middlewares/response.js similarity index 100% rename from website/server/middlewares/api-v3/response.js rename to website/server/middlewares/response.js diff --git a/website/server/middlewares/api-v3/setupBody.js b/website/server/middlewares/setupBody.js similarity index 100% rename from website/server/middlewares/api-v3/setupBody.js rename to website/server/middlewares/setupBody.js diff --git a/website/server/middlewares/api-v3/static.js b/website/server/middlewares/static.js similarity index 86% rename from website/server/middlewares/api-v3/static.js rename to website/server/middlewares/static.js index 95736fcaec..405deaa655 100644 --- a/website/server/middlewares/api-v3/static.js +++ b/website/server/middlewares/static.js @@ -4,8 +4,8 @@ import path from 'path'; const IS_PROD = nconf.get('IS_PROD'); const MAX_AGE = IS_PROD ? 31536000000 : 0; -const PUBLIC_DIR = path.join(__dirname, '/../../../client'); -const BUILD_DIR = path.join(__dirname, '/../../../build'); +const PUBLIC_DIR = path.join(__dirname, '/../../client'); +const BUILD_DIR = path.join(__dirname, '/../../build'); module.exports = function staticMiddleware (expressApp) { // TODO move all static files to a single location (one for public and one for build) diff --git a/website/server/middlewares/api-v3/v1.js b/website/server/middlewares/v1.js similarity index 83% rename from website/server/middlewares/api-v3/v1.js rename to website/server/middlewares/v1.js index abe26f42e6..c91c65b617 100644 --- a/website/server/middlewares/api-v3/v1.js +++ b/website/server/middlewares/v1.js @@ -5,14 +5,14 @@ import express from 'express'; import nconf from 'nconf'; import { NotFound, -} from '../../libs/api-v3/errors'; +} from '../libs/errors'; const router = express.Router(); // eslint-disable-line babel/new-cap const BASE_URL = nconf.get('BASE_URL'); router.all('*', function deprecatedV1 (req, res, next) { - let error = new NotFound(`API v1 is no longer supported, please use API v3 instead (${BASE_URL}/static/api).`); + let error = new NotFound(`API v1 is no longer supported, please use API v3 instead (${BASE_URL}/apidoc).`); return next(error); }); diff --git a/website/server/middlewares/v2.js b/website/server/middlewares/v2.js new file mode 100644 index 0000000000..7b35de3b72 --- /dev/null +++ b/website/server/middlewares/v2.js @@ -0,0 +1,19 @@ +// API v2 middlewares and routes +// DEPRECATED AND INACTIVE + +import express from 'express'; +import nconf from 'nconf'; +import { + NotFound, +} from '../libs/errors'; + +const router = express.Router(); // eslint-disable-line babel/new-cap + +const BASE_URL = nconf.get('BASE_URL'); + +router.all('*', function deprecatedV2 (req, res, next) { + let error = new NotFound(`API v2 is no longer supported, please use API v3 instead (${BASE_URL}/apidoc).`); + return next(error); +}); + +module.exports = router; diff --git a/website/server/middlewares/api-v3/v3.js b/website/server/middlewares/v3.js similarity index 73% rename from website/server/middlewares/api-v3/v3.js rename to website/server/middlewares/v3.js index f9aa637fa0..d7afccb28c 100644 --- a/website/server/middlewares/api-v3/v3.js +++ b/website/server/middlewares/v3.js @@ -2,17 +2,17 @@ import express from 'express'; import expressValidator from 'express-validator'; import analytics from './analytics'; import setupBody from './setupBody'; -import routes from '../../libs/api-v3/routes'; +import routes from '../libs/routes'; import path from 'path'; -const API_CONTROLLERS_PATH = path.join(__dirname, '/../../controllers/api-v3/'); -const TOP_LEVEL_CONTROLLERS_PATH = path.join(__dirname, '/../../controllers/top-level/'); +const API_CONTROLLERS_PATH = path.join(__dirname, '/../controllers/api-v3/'); +const TOP_LEVEL_CONTROLLERS_PATH = path.join(__dirname, '/../controllers/top-level/'); const v3app = express(); // re-set the view options because they are not inherited from the top level app v3app.set('view engine', 'jade'); -v3app.set('views', `${__dirname}/../../../views`); +v3app.set('views', `${__dirname}/../../views`); v3app.use(expressValidator()); v3app.use(analytics); diff --git a/website/server/models/challenge.js b/website/server/models/challenge.js index 9f6fded6f7..902b23171a 100644 --- a/website/server/models/challenge.js +++ b/website/server/models/challenge.js @@ -1,7 +1,7 @@ import mongoose from 'mongoose'; import Bluebird from 'bluebird'; import validator from 'validator'; -import baseModel from '../libs/api-v3/baseModel'; +import baseModel from '../libs/baseModel'; import _ from 'lodash'; import * as Tasks from './task'; import { model as User } from './user'; @@ -9,10 +9,10 @@ import { model as Group, TAVERN_ID, } from './group'; -import { removeFromArray } from '../libs/api-v3/collectionManipulators'; +import { removeFromArray } from '../libs/collectionManipulators'; import shared from '../../../common'; -import { sendTxn as txnEmail } from '../libs/api-v3/email'; -import sendPushNotification from '../libs/api-v3/pushNotifications'; +import { sendTxn as txnEmail } from '../libs/email'; +import sendPushNotification from '../libs/pushNotifications'; import cwait from 'cwait'; const Schema = mongoose.Schema; @@ -332,105 +332,4 @@ schema.methods.closeChal = async function closeChal (broken = {}) { Bluebird.all(backgroundTasks); }; -// Methods to adapt the new schema to API v2 responses (mostly tasks inside the challenge model) -// These will be removed once API v2 is discontinued - -// Get all the tasks belonging to a challenge, -schema.methods.getTasks = function getChallengeTasks () { - let args = Array.from(arguments); - let cb; - let type; - - if (args.length === 1) { - cb = args[0]; - } else if (args.length > 1) { - type = args[0]; - cb = args[1]; - } else { - cb = function noop () {}; - } - - let query = { - userId: { - $exists: false, - }, - - 'challenge.id': this._id, - }; - - if (type) query.type = type; - - return Tasks.Task.find(query, cb); // so we can use it as a promise -}; - -// Given challenge and an array of tasks and one of members return an API compatible challenge + tasks obj + members -schema.methods.addToChallenge = function addToChallenge (tasks, members) { - let obj = this.toJSON(); - obj.members = members; - - let tasksOrder = obj.tasksOrder; // Saving a reference because we won't return it - - obj.habits = []; - obj.dailys = []; - obj.todos = []; - obj.rewards = []; - - obj.tasksOrder = undefined; - let unordered = []; - - tasks.forEach((task) => { - // We want to push the task at the same position where it's stored in tasksOrder - let pos = tasksOrder[`${task.type}s`].indexOf(task._id); - if (pos === -1) { // Should never happen, it means the lists got out of sync - unordered.push(task.toJSONV2()); - } else { - obj[`${task.type}s`][pos] = task.toJSONV2(); - } - }); - - // Reconcile unordered items - unordered.forEach((task) => { - obj[`${task.type}s`].push(task); - }); - - // Remove null values that can be created when inserting tasks at an index > length - ['habits', 'dailys', 'rewards', 'todos'].forEach((type) => { - obj[type] = _.compact(obj[type]); - }); - - return obj; -}; - -// Return the data maintaining backward compatibility -schema.methods.getTransformedData = function getTransformedData (options) { - let self = this; - - let cb = options.cb; - let populateMembers = options.populateMembers; - - let queryMembers = { - challenges: self._id, - }; - - let selectDataMembers = '_id'; - - if (populateMembers) { - selectDataMembers += ` ${populateMembers}`; - } - - let membersQuery = User.find(queryMembers).select(selectDataMembers); - if (options.limitPopulation) membersQuery.limit(15); - - Bluebird.all([ - membersQuery.exec(), - self.getTasks(), - ]) - .then((results) => { - cb(null, self.addToChallenge(results[1], results[0])); - }) - .catch(cb); -}; - -// END of API v2 methods - export let model = mongoose.model('Challenge', schema); diff --git a/website/server/models/coupon.js b/website/server/models/coupon.js index af9953aba0..43787f5c03 100644 --- a/website/server/models/coupon.js +++ b/website/server/models/coupon.js @@ -4,11 +4,11 @@ import mongoose from 'mongoose'; import _ from 'lodash'; import shared from '../../../common'; import couponCode from 'coupon-code'; -import baseModel from '../libs/api-v3/baseModel'; +import baseModel from '../libs/baseModel'; import { BadRequest, NotAuthorized, -} from '../libs/api-v3/errors'; +} from '../libs/errors'; export let schema = new mongoose.Schema({ _id: {type: String, default: couponCode.generate}, diff --git a/website/server/models/emailUnsubscription.js b/website/server/models/emailUnsubscription.js index fe30e5d608..a0d1d0b7e6 100644 --- a/website/server/models/emailUnsubscription.js +++ b/website/server/models/emailUnsubscription.js @@ -1,6 +1,6 @@ import mongoose from 'mongoose'; import validator from 'validator'; -import baseModel from '../libs/api-v3/baseModel'; +import baseModel from '../libs/baseModel'; // A collection used to store mailing list unsubscription for non registered email addresses export let schema = new mongoose.Schema({ diff --git a/website/server/models/group.js b/website/server/models/group.js index fc51fef7cb..7abbe42135 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -7,17 +7,17 @@ import shared from '../../../common'; import _ from 'lodash'; import { model as Challenge} from './challenge'; import validator from 'validator'; -import { removeFromArray } from '../libs/api-v3/collectionManipulators'; +import { removeFromArray } from '../libs/collectionManipulators'; import { InternalServerError, BadRequest, -} from '../libs/api-v3/errors'; -import baseModel from '../libs/api-v3/baseModel'; -import { sendTxn as sendTxnEmail } from '../libs/api-v3/email'; +} from '../libs/errors'; +import baseModel from '../libs/baseModel'; +import { sendTxn as sendTxnEmail } from '../libs/email'; import Bluebird from 'bluebird'; import nconf from 'nconf'; -import sendPushNotification from '../libs/api-v3/pushNotifications'; -import pusher from '../libs/api-v3/pusher'; +import sendPushNotification from '../libs/pushNotifications'; +import pusher from '../libs/pusher'; const questScrolls = shared.content.quests; const Schema = mongoose.Schema; @@ -749,63 +749,6 @@ schema.methods.leave = async function leaveGroup (user, keep = 'keep-all') { return await Bluebird.all(promises); }; -// API v2 compatibility methods -schema.methods.getTransformedData = function getTransformedData (options) { - let cb = options.cb; - let populateMembers = options.populateMembers; - let populateInvites = options.populateInvites; - let populateChallenges = options.populateChallenges; - - let obj = this.toJSON(); - - let queryMembers = {}; - let queryInvites = {}; - - if (this.type === 'guild') { - queryInvites['invitations.guilds.id'] = this._id; - } else { - queryInvites['invitations.party.id'] = this._id; - } - - if (this.type === 'guild') { - queryMembers.guilds = this._id; - } else { - queryMembers['party._id'] = this._id; - } - - let selectDataMembers = '_id'; - let selectDataInvites = '_id'; - let selectDataChallenges = '_id'; - - if (populateMembers) { - selectDataMembers += ` ${populateMembers}`; - } - if (populateInvites) { - selectDataInvites += ` ${populateInvites}`; - } - if (populateChallenges) { - selectDataChallenges += ` ${populateChallenges}`; - } - - let membersQuery = User.find(queryMembers).select(selectDataMembers); - if (options.limitPopulation) membersQuery.limit(15); - - Bluebird.all([ - membersQuery.exec(), - User.find(queryInvites).select(populateInvites).exec(), - Challenge.find({group: obj._id}).select(populateMembers).exec(), - ]) - .then((results) => { - obj.members = results[0]; - obj.invites = results[1]; - obj.challenges = results[2]; - - cb(null, obj); - }) - .catch(cb); -}; -// END API v2 compatibility methods - export let model = mongoose.model('Group', schema); // initialize tavern if !exists (fresh installs) diff --git a/website/server/models/iapPurchaseReceipt.js b/website/server/models/iapPurchaseReceipt.js index 6450ded11f..b29bd90425 100644 --- a/website/server/models/iapPurchaseReceipt.js +++ b/website/server/models/iapPurchaseReceipt.js @@ -1,5 +1,5 @@ import mongoose from 'mongoose'; -import baseModel from '../libs/api-v3/baseModel'; +import baseModel from '../libs/baseModel'; import validator from 'validator'; const Schema = mongoose.Schema; diff --git a/website/server/models/pushDevice.js b/website/server/models/pushDevice.js index bab33983e5..2467f6e963 100644 --- a/website/server/models/pushDevice.js +++ b/website/server/models/pushDevice.js @@ -1,5 +1,5 @@ import mongoose from 'mongoose'; -import baseModel from '../libs/api-v3/baseModel'; +import baseModel from '../libs/baseModel'; const Schema = mongoose.Schema; diff --git a/website/server/models/tag.js b/website/server/models/tag.js index 77938ab7d9..0f2ed2abaa 100644 --- a/website/server/models/tag.js +++ b/website/server/models/tag.js @@ -1,5 +1,5 @@ import mongoose from 'mongoose'; -import baseModel from '../libs/api-v3/baseModel'; +import baseModel from '../libs/baseModel'; import { v4 as uuid } from 'uuid'; import validator from 'validator'; diff --git a/website/server/models/task.js b/website/server/models/task.js index e1ba2645d7..a35e4b6c42 100644 --- a/website/server/models/task.js +++ b/website/server/models/task.js @@ -2,10 +2,10 @@ import mongoose from 'mongoose'; import shared from '../../../common'; import validator from 'validator'; import moment from 'moment'; -import baseModel from '../libs/api-v3/baseModel'; -import { InternalServerError } from '../libs/api-v3/errors'; +import baseModel from '../libs/baseModel'; +import { InternalServerError } from '../libs/errors'; import _ from 'lodash'; -import { preenHistory } from '../libs/api-v3/preening'; +import { preenHistory } from '../libs/preening'; const Schema = mongoose.Schema; @@ -22,7 +22,6 @@ export let tasksTypes = ['habit', 'daily', 'todo', 'reward']; // Important // When something changes here remember to update the client side model at common/script/libs/taskDefaults export let TaskSchema = new Schema({ - _legacyId: String, // TODO Remove when v2 is deprecated type: {type: String, enum: tasksTypes, required: true, default: tasksTypes[0]}, text: {type: String, required: true}, notes: {type: String, default: ''}, @@ -160,46 +159,6 @@ TaskSchema.methods.scoreChallengeTask = async function scoreChallengeTask (delta await chalTask.save(); }; - -// Methods to adapt the new schema to API v2 responses (mostly tasks inside the user model) -// These will be removed once API v2 is discontinued - -// toJSON for API v2 -TaskSchema.methods.toJSONV2 = function toJSONV2 () { - let toJSON = this.toJSON(); - if (toJSON._legacyId) { - toJSON.id = toJSON._legacyId; - } else { - toJSON.id = toJSON._id; - } - - if (!toJSON.challenge) toJSON.challenge = {}; - - let v3Tags = this.tags; - - toJSON.tags = {}; - v3Tags.forEach(tag => { - toJSON.tags[tag] = true; - }); - - toJSON.dateCreated = this.createdAt; - - return toJSON; -}; - -TaskSchema.statics.fromJSONV2 = function fromJSONV2 (taskObj) { - if (taskObj.id) taskObj._id = taskObj.id; - - let v2Tags = taskObj.tags || {}; - - taskObj.tags = []; - taskObj.tags = _.map(v2Tags, (tag, key) => key); - - return taskObj; -}; - -// END of API v2 methods - export let Task = mongoose.model('Task', TaskSchema); Task.schema.path('alias').validate(function valiateAliasNotTaken (alias, respond) { diff --git a/website/server/models/user/hooks.js b/website/server/models/user/hooks.js index 04ade1fa11..c2759fcfde 100644 --- a/website/server/models/user/hooks.js +++ b/website/server/models/user/hooks.js @@ -3,7 +3,7 @@ import _ from 'lodash'; import moment from 'moment'; import * as Tasks from '../task'; import Bluebird from 'bluebird'; -import baseModel from '../../libs/api-v3/baseModel'; +import baseModel from '../../libs/baseModel'; import schema from './schema'; diff --git a/website/server/models/user/methods.js b/website/server/models/user/methods.js index 0b6a0469f3..9dc794f86b 100644 --- a/website/server/models/user/methods.js +++ b/website/server/models/user/methods.js @@ -1,6 +1,4 @@ import shared from '../../../../common'; -import _ from 'lodash'; -import * as Tasks from '../task'; import Bluebird from 'bluebird'; import { chatDefaults, @@ -42,88 +40,4 @@ schema.methods.addNotification = function addUserNotification (type, data = {}) type, data, }); -}; - -// Methods to adapt the new schema to API v2 responses (mostly tasks inside the user model) -// These will be removed once API v2 is discontinued - -// Get all the tasks belonging to a user, -schema.methods.getTasks = function getUserTasks () { - let args = Array.from(arguments); - let cb; - let type; - - if (args.length === 1) { - cb = args[0]; - } else { - type = args[0]; - cb = args[1]; - } - - let query = { - userId: this._id, - }; - - if (type) query.type = type; - - Tasks.Task.find(query, cb); -}; - -// Given user and an array of tasks, return an API compatible user + tasks obj -schema.methods.addTasksToUser = function addTasksToUser (tasks) { - let obj = this.toJSON(); - - obj.id = obj._id; - obj.filters = {}; - - obj.tags = obj.tags.map(tag => { - return { - id: tag.id, - name: tag.name, - challenge: tag.challenge, - }; - }); - - let tasksOrder = obj.tasksOrder; // Saving a reference because we won't return it - - obj.habits = []; - obj.dailys = []; - obj.todos = []; - obj.rewards = []; - - obj.tasksOrder = undefined; - let unordered = []; - - tasks.forEach((task) => { - // We want to push the task at the same position where it's stored in tasksOrder - let pos = tasksOrder[`${task.type}s`].indexOf(task._id); - if (pos === -1) { // Should never happen, it means the lists got out of sync - unordered.push(task.toJSONV2()); - } else { - obj[`${task.type}s`][pos] = task.toJSONV2(); - } - }); - - // Reconcile unordered items - unordered.forEach((task) => { - obj[`${task.type}s`].push(task); - }); - - // Remove null values that can be created when inserting tasks at an index > length - ['habits', 'dailys', 'rewards', 'todos'].forEach((type) => { - obj[type] = _.compact(obj[type]); - }); - - return obj; -}; - -// Return the data maintaining backward compatibility -schema.methods.getTransformedData = function getTransformedData (cb) { - let self = this; - this.getTasks((err, tasks) => { - if (err) return cb(err); - cb(null, self.addTasksToUser(tasks)); - }); -}; - -// END of API v2 methods \ No newline at end of file +}; \ No newline at end of file diff --git a/website/server/models/userNotification.js b/website/server/models/userNotification.js index f0b0c58211..9d72831a13 100644 --- a/website/server/models/userNotification.js +++ b/website/server/models/userNotification.js @@ -1,5 +1,5 @@ import mongoose from 'mongoose'; -import baseModel from '../libs/api-v3/baseModel'; +import baseModel from '../libs/baseModel'; import { v4 as uuid } from 'uuid'; import validator from 'validator'; diff --git a/website/server/routes/api-v2/auth.js b/website/server/routes/api-v2/auth.js deleted file mode 100644 index 66d1bebb3a..0000000000 --- a/website/server/routes/api-v2/auth.js +++ /dev/null @@ -1,20 +0,0 @@ -var auth = require('../../controllers/api-v2/auth'); -var express = require('express'); -var i18n = require('../../libs/api-v2/i18n'); -var router = express.Router(); -import { - getUserLanguage -} from '../../middlewares/api-v3/language'; - -/* auth.auth*/ -// auth.setupPassport(router); //TODO make this consistent with the others -router.post('/register', getUserLanguage, auth.registerUser); -router.post('/user/auth/local', getUserLanguage, auth.loginLocal); -router.post('/user/auth/social', getUserLanguage, auth.loginSocial); -router.delete('/user/auth/social', getUserLanguage, auth.auth, auth.deleteSocial); -router.post('/user/reset-password', getUserLanguage, auth.resetPassword); -router.post('/user/change-password', getUserLanguage, auth.auth, auth.changePassword); -router.post('/user/change-username', getUserLanguage, auth.auth, auth.changeUsername); -router.post('/user/change-email', getUserLanguage, auth.auth, auth.changeEmail); - -module.exports = router; diff --git a/website/server/routes/api-v2/coupon.js b/website/server/routes/api-v2/coupon.js deleted file mode 100644 index 7caee3f815..0000000000 --- a/website/server/routes/api-v2/coupon.js +++ /dev/null @@ -1,15 +0,0 @@ -var nconf = require('nconf'); -var express = require('express'); -var router = express.Router(); -var auth = require('../../controllers/api-v2/auth'); -var coupon = require('../../controllers/api-v2/coupon'); -var i18n = require('../../libs/api-v2/i18n'); -import { - getUserLanguage -} from '../../middlewares/api-v3/language'; - -router.get('/coupons', auth.authWithUrl, getUserLanguage, coupon.ensureAdmin, coupon.getCoupons); -router.post('/coupons/generate/:event', auth.auth, getUserLanguage, coupon.ensureAdmin, coupon.generateCoupons); -router.post('/user/coupon/:code', auth.auth, getUserLanguage, coupon.enterCode); - -module.exports = router; diff --git a/website/server/routes/api-v2/swagger.js b/website/server/routes/api-v2/swagger.js deleted file mode 100644 index c60d8364b6..0000000000 --- a/website/server/routes/api-v2/swagger.js +++ /dev/null @@ -1,778 +0,0 @@ -/* ----------- /api/v2 API ------------ -see https://github.com/wordnik/swagger-node-express -Every url added to router is prefaced by /api/v2 -Note: Many user-route ops exist in ../../common/script/index.js#user.ops, so that they can (1) be called both -client and server. -v1 user. Requires x-api-user (user id) and x-api-key (api key) headers, Test with: - */ - -var user = require("../../controllers/api-v2/user"); -var groups = require("../../controllers/api-v2/groups"); -var members = require("../../controllers/api-v2/members"); -var auth = require("../../controllers/api-v2/auth"); -var hall = require("../../controllers/api-v2/hall"); -var challenges = require("../../controllers/api-v2/challenges"); -var dataexport = require("../../controllers/api-v2/dataexport"); -var nconf = require("nconf"); -var cron = user.cron; -var _ = require('lodash'); -var content = require('../../../../common').content; -var i18n = require('../../libs/api-v2/i18n'); -import { - getUserLanguage -} from '../../middlewares/api-v3/language'; -var forceRefresh = require('../../middlewares/forceRefresh').middleware; - -module.exports = function(swagger, v2) { - var path = swagger.pathParam; - var body = swagger.bodyParam; - var query = swagger.queryParam; - - swagger.setAppHandler(v2); - swagger.setErrorHandler("next"); - swagger.setHeaders = function() {}; - swagger.configureSwaggerPaths("", "/api-docs", ""); - - var api = { - '/status': { - spec: { - description: "Returns the status of the server (up or down). Does not require authentication." - }, - action: function(req, res) { - return res.json({ - status: "up" - }); - } - }, - '/content': { - spec: { - description: "Get all available content objects. This is essential, since Habit often depends on item keys (eg, when purchasing a weapon). Does not require authentication.", - parameters: [query("language", "Optional language to use for content's strings. Default is english.", "string")] - }, - action: user.getContent - }, - '/content/paths': { - spec: { - description: "Show user model tree. Does not require authentication." - }, - action: user.getModelPaths - }, - "/export/history": { - spec: { - description: "Export user history", - method: 'GET' - }, - middleware: [auth.auth, getUserLanguage], - action: dataexport.history - }, - "/user/tasks/{id}/{direction}": { - spec: { - description: "Simple scoring of a task (Habit, Daily, To-Do, or Reward). This is most-likely the only API route you'll be using as a 3rd-party developer. The most common operation is for the user to gain or lose points based on some action (browsing Reddit, running a mile, 1 Pomodor, etc). Call this route, if the task you're trying to score doesn't exist, it will be created for you. When random events occur, the user._tmp variable will be filled. Critical hits can be accessed through user._tmp.crit. The Streakbonus can be accessed through user._tmp.streakBonus. Both will contain the multiplier value. When random drops occur, the following values are available: user._tmp.drop = {text,type,dialog,value,key,notes}", - parameters: [path("id", "ID of the task to score. If this task doesn't exist, a task will be created automatically", "string"), path("direction", "Either 'up' or 'down'", "string"), body('', "If you're creating a 3rd-party task, pass up any task attributes in the body (see TaskSchema).", 'object')], - method: 'POST' - }, - action: user.score - }, - "/user/tasks:GET": { - spec: { - path: '/user/tasks', - description: "Get all user's tasks" - }, - action: user.getTasks - }, - "/user/tasks:POST": { - spec: { - path: '/user/tasks', - description: "Create a task", - method: 'POST', - parameters: [body("", "Send up the whole task (see TaskSchema)", "object")] - }, - action: user.addTask - }, - "/user/tasks/{id}:GET": { - spec: { - path: '/user/tasks/{id}', - description: "Get an individual task", - parameters: [path("id", "Task ID", "string")] - }, - action: user.getTask - }, - "/user/tasks/{id}:PUT": { - spec: { - path: '/user/tasks/{id}', - description: "Update a user's task", - method: 'PUT', - parameters: [path("id", "Task ID", "string"), body("", "Send up the whole task (see TaskSchema)", "object")] - }, - action: user.updateTask - }, - "/user/tasks/{id}:DELETE": { - spec: { - path: '/user/tasks/{id}', - description: "Delete a task", - method: 'DELETE', - parameters: [path("id", "Task ID", "string")] - }, - action: user.deleteTask - }, - "/user/tasks/{id}/sort": { - spec: { - method: 'POST', - description: 'Sort tasks', - parameters: [path("id", "Task ID", "string"), query("from", "Index where you're sorting from (0-based)", "integer"), query("to", "Index where you're sorting to (0-based)", "integer")] - }, - action: user.sortTask - }, - "/user/tasks/clear-completed": { - spec: { - method: 'POST', - description: "Clears competed To-Dos (needed periodically for performance)." - }, - action: user.clearCompleted - }, - "/user/tasks/{id}/unlink": { - spec: { - method: 'POST', - description: 'Unlink a task from its challenge', - parameters: [path("id", "Task ID", "string"), query('keep', "When unlinking a challenge task, how to handle the orphans?", 'string', ['keep', 'keep-all', 'remove', 'remove-all'])] - }, - middleware: [auth.auth, getUserLanguage], - action: challenges.unlink - }, - "/user/inventory/buy": { - spec: { - description: "Get a list of buyable gear" - }, - action: user.getBuyList - }, - "/user/inventory/buy/{key}": { - spec: { - method: 'POST', - description: "Buy a gear piece and equip it automatically", - parameters: [path('key', "The key of the item to buy (call /content route for available keys)", 'string', _.keys(content.gear.flat))] - }, - action: user.buy - }, - "/user/inventory/sell/{type}/{key}": { - spec: { - method: 'POST', - description: "Sell inventory items back to Alexander", - parameters: [path('type', "The type of object you're selling back.", 'string', ['eggs', 'hatchingPotions', 'food']), path('key', "The object key you're selling back (call /content route for available keys)", 'string')] - }, - action: user.sell - }, - "/user/inventory/purchase/{type}/{key}": { - spec: { - method: 'POST', - description: "Purchase a Gem-purchasable item from Alexander", - parameters: [path('type', "The type of object you're purchasing.", 'string', ['eggs', 'hatchingPotions', 'food', 'quests', 'special']), path('key', "The object key you're purchasing (call /content route for available keys)", 'string')] - }, - action: user.purchase - }, - "/user/inventory/hourglass/{type}/{key}": { - spec: { - method: 'POST', - description: "Purchase a pet or mount using a Mystic Hourglass", - parameters: [path('type', "The type of object you're purchasing.", 'string', ['pets', 'mounts']), path('key', "The object key you're purchasing (call /content route for available keys)", 'string')] - }, - action: user.hourglassPurchase - }, - "/user/inventory/mystery/{key}": { - spec: { - method: 'POST', - description: "Purchase a Mystery Item Set using a Mystic Hourglass", - parameters: [path('key', "The key for the Mystery Set you're purchasing (call /content route for available keys)", 'string')] - }, - action: user.buyMysterySet - }, - "/user/inventory/feed/{pet}/{food}": { - spec: { - method: 'POST', - description: "Feed your pet some food", - parameters: [path('pet', "The key of the pet you're feeding", 'string', _.keys(content.pets)), path('food', "The key of the food to feed your pet", 'string', _.keys(content.food))] - }, - action: user.feed - }, - "/user/inventory/equip/{type}/{key}": { - spec: { - method: 'POST', - description: "Equip an item (either pet, mount, equipped or costume)", - parameters: [path('type', "Type to equip", 'string', ['pet', 'mount', 'equipped', 'costume']), path('key', "The object key you're equipping (call /content route for available keys)", 'string')] - }, - action: user.equip - }, - "/user/inventory/hatch/{egg}/{hatchingPotion}": { - spec: { - method: 'POST', - description: "Pour a hatching potion on an egg", - parameters: [path('egg', "The egg key to hatch", 'string', _.keys(content.eggs)), path('hatchingPotion', "The hatching potion to pour", 'string', _.keys(content.hatchingPotions))] - }, - action: user.hatch - }, - "/user:GET": { - spec: { - path: '/user', - description: "Get the full user object" - }, - action: user.getUser - }, - "/user/anonymized": { - spec: { - description: "Get the user object without any personal data" - }, - action: user.getUserAnonymized - }, - "/user:PUT": { - spec: { - path: '/user', - method: 'PUT', - description: "Update the user object (only certain attributes are supported)", - parameters: [body('', 'The user object (see UserSchema)', 'object')] - }, - action: user.update - }, - "/user:DELETE": { - spec: { - path: '/user', - method: 'DELETE', - description: "Delete a user object entirely, USE WITH CAUTION!" - }, - middleware: [auth.auth, getUserLanguage], - action: user["delete"] - }, - "/user/revive": { - spec: { - method: 'POST', - description: "Revive your dead user" - }, - action: user.revive - }, - "/user/reroll": { - spec: { - method: 'POST', - description: 'Drink the Fortify Potion (Note, it used to be called re-roll)' - }, - action: user.reroll - }, - "/user/reset": { - spec: { - method: 'POST', - description: "Completely reset your account" - }, - action: user.reset - }, - "/user/sleep": { - spec: { - method: 'POST', - description: "Toggle whether you're resting in the inn" - }, - action: user.sleep - }, - "/user/rebirth": { - spec: { - method: 'POST', - description: "Rebirth your avatar" - }, - action: user.rebirth - }, - "/user/class/change": { - spec: { - method: 'POST', - description: "Either remove your avatar's class, or change it to something new", - parameters: [query('class', "The key of the class to change to. If not provided, user's class is removed.", 'string', ['warrior', 'healer', 'rogue', 'wizard', ''])] - }, - action: user.changeClass - }, - "/user/class/allocate": { - spec: { - method: 'POST', - description: "Allocate one point towards an attribute", - parameters: [query('stat', 'The stat to allocate towards', 'string', ['str', 'per', 'int', 'con'])] - }, - action: user.allocate - }, - "/user/class/cast/{spell}": { - spec: { - method: 'POST', - description: "Casts a spell on a target.", - parameters: [path('spell', "The key of the spell to cast (see ../../common#content/index.js)", 'string'), query('targetType', "The type of object you're targeting", 'string', ['party', 'self', 'user', 'task']), query('targetId', "The ID of the object you're targeting", 'string')] - }, - action: user.cast - }, - "/user/unlock": { - spec: { - method: 'POST', - description: "Unlock a certain gem-purchaseable path (or multiple paths)", - parameters: [query('path', "The path to unlock, such as hair.green or shirts.red,shirts.blue", 'string')] - }, - action: user.unlock - }, - "/user/batch-update": { - spec: { - method: 'POST', - description: "This is an advanced route which is useful for apps which might for example need offline support. You can send a whole batch of user-based operations, which allows you to queue them up offline and send them all at once. The format is {op:'nameOfOperation',parameters:{},body:{},query:{}}", - parameters: [body('', 'The array of batch-operations to perform', 'object')] - }, - middleware: [forceRefresh, auth.auth, getUserLanguage, cron, user.sessionPartyInvite], - action: user.batchUpdate - }, - "/user/tags/{id}:GET": { - spec: { - path: '/user/tags/{id}', - method: 'GET', - description: "Get a tag", - parameters: [path('id', 'The id of the tag to get', 'string')] - }, - action: user.getTag - }, - "/user/tags:POST": { - spec: { - path: "/user/tags", - method: 'POST', - description: 'Create a new tag', - parameters: [body('', 'New tag (see UserSchema.tags)', 'object')] - }, - action: user.addTag - }, - "/user/tags:GET": { - spec: { - path: "/user/tags", - method: 'GET', - description: 'List all of a user\'s tags' - }, - action: user.getTags - }, - "/user/tags/sort": { - spec: { - method: 'POST', - description: 'Sort tags', - parameters: [query("from", "Index where you're sorting from (0-based)", "integer"), query("to", "Index where you're sorting to (0-based)", "integer")] - }, - action: user.sortTag - }, - "/user/tags/{id}:PUT": { - spec: { - path: '/user/tags/{id}', - method: 'PUT', - description: "Edit a tag", - parameters: [path('id', 'The id of the tag to edit', 'string'), body('', 'Tag edits (see UserSchema.tags)', 'object')] - }, - action: user.updateTag - }, - "/user/tags/{id}:DELETE": { - spec: { - path: '/user/tags/{id}', - method: 'DELETE', - description: 'Delete a tag', - parameters: [path('id', 'Id of tag to delete', 'string')] - }, - action: user.deleteTag - }, - "/user/webhooks": { - spec: { - method: 'POST', - description: 'Create a new webhook', - parameters: [body('', 'New Webhook {url:"webhook endpoint (required)", id:"id of webhook (shared.uuid(), optional)", enabled:"whether webhook is enabled (true by default, optional)"}', 'object')] - }, - action: user.addWebhook - }, - "/user/webhooks/{id}:PUT": { - spec: { - path: '/user/webhooks/{id}', - method: 'PUT', - description: "Edit a webhook", - parameters: [path('id', 'The id of the webhook to edit', 'string'), body('', 'New Webhook {url:"webhook endpoint (required)", id:"id of webhook (shared.uuid(), optional)", enabled:"whether webhook is enabled (true by default, optional)"}', 'object')] - }, - action: user.updateWebhook - }, - "/user/webhooks/{id}:DELETE": { - spec: { - path: '/user/webhooks/{id}', - method: 'DELETE', - description: 'Delete a webhook', - parameters: [path('id', 'Id of webhook to delete', 'string')] - }, - action: user.deleteWebhook - }, - "/user/pushDevice": { - spec: { - method: 'POST', - description: 'Add a new push devices registration ID', - parameters: [body('', 'New push registration { regId: "123123", type: "android"}', 'object')] - }, - action: user.addPushDevice - }, - "/groups:GET": { - spec: { - path: '/groups', - description: "Get a list of groups", - parameters: [query('type', "Comma-separated types of groups to return, eg 'party,guilds,public,tavern'", 'string')] - }, - middleware: [auth.auth, getUserLanguage], - action: groups.list - }, - "/groups:POST": { - spec: { - path: '/groups', - method: 'POST', - description: 'Create a group', - parameters: [body('', 'Group object (see GroupSchema)', 'object')] - }, - middleware: [auth.auth, getUserLanguage], - action: groups.create - }, - "/groups/{gid}:GET": { - spec: { - path: '/groups/{gid}', - description: "Get a group. The party the user currently is in can be accessed with the gid 'party'.", - parameters: [path('gid', 'Group ID', 'string')] - }, - middleware: [auth.auth, getUserLanguage], - action: groups.get - }, - "/groups/{gid}:POST": { - spec: { - path: '/groups/{gid}', - method: 'POST', - description: "Edit a group", - parameters: [body('', 'Group object (see GroupSchema)', 'object')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.update - }, - "/groups/{gid}/join": { - spec: { - method: 'POST', - description: 'Join a group', - parameters: [path('gid', 'Id of the group to join', 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.join - }, - "/groups/{gid}/leave": { - spec: { - method: 'POST', - description: 'Leave a group', - parameters: [path('gid', 'ID of the group to leave', 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.leave - }, - "/groups/{gid}/invite": { - spec: { - method: 'POST', - description: "Invite a user to a group", - parameters: [path('gid', 'Group id', 'string'), body('', 'a payload of invites either under body.uuids or body.emails, only one of them!', 'object')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.invite - }, - "/groups/{gid}/removeMember": { - spec: { - method: 'POST', - description: "Remove / boot a member from a group", - parameters: [path('gid', 'Group id', 'string'), query('uuid', 'User id to boot', 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.removeMember - }, - "/groups/{gid}/questAccept": { - spec: { - method: 'POST', - description: "Accept a quest invitation", - parameters: [path('gid', "Group id", 'string'), query('key', "optional. if provided, trigger new invite, if not, accept existing invite", 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.questAccept - }, - "/groups/{gid}/questReject": { - spec: { - method: 'POST', - description: 'Reject quest invitation', - parameters: [path('gid', 'Group id', 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.questReject - }, - "/groups/{gid}/questCancel": { - spec: { - method: 'POST', - description: 'Cancel quest before it starts (in invitation stage)', - parameters: [path('gid', 'Group to cancel quest in', 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.questCancel - }, - "/groups/{gid}/questAbort": { - spec: { - method: 'POST', - description: 'Abort quest after it has started (all progress will be lost)', - parameters: [path('gid', 'Group to abort quest in', 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.questAbort - }, - "/groups/{gid}/questLeave": { - spec: { - method: 'POST', - description: 'Leave an active quest (Quest leaders cannot leave active quests. They must abort the quest to leave)', - parameters: [path('gid', 'Group to leave quest in', 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.questLeave - }, - "/groups/{gid}/chat:GET": { - spec: { - path: "/groups/{gid}/chat", - description: "Get all chat messages", - parameters: [path('gid', 'Group to return the chat from ', 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.getChat - }, - "/groups/{gid}/chat:POST": { - spec: { - method: 'POST', - path: "/groups/{gid}/chat", - description: "Send a chat message", - parameters: [query('message', 'Chat message', 'string'), path('gid', 'Group id', 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.postChat - }, - "/groups/{gid}/chat/seen": { - spec: { - method: 'POST', - description: "Flag chat messages for a particular group as seen", - parameters: [path('gid', 'Group id', 'string')] - }, - action: groups.seenMessage - }, - "/groups/{gid}/chat/{messageId}": { - spec: { - method: 'DELETE', - description: 'Delete a chat message in a given group', - parameters: [path('gid', 'ID of the group containing the message to be deleted', 'string'), path('messageId', 'ID of message to be deleted', 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.deleteChatMessage - }, - "/groups/{gid}/chat/{mid}/like": { - spec: { - method: 'POST', - description: "Like a chat message", - parameters: [path('gid', 'Group id', 'string'), path('mid', 'Message id', 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.likeChatMessage - }, - "/groups/{gid}/chat/{mid}/flag": { - spec: { - method: 'POST', - description: "Flag a chat message", - parameters: [path('gid', 'Group id', 'string'), path('mid', 'Message id', 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.flagChatMessage - }, - "/groups/{gid}/chat/{mid}/clearflags": { - spec: { - method: 'POST', - description: "Clear flag count from message and unhide it", - parameters: [path('gid', 'Group id', 'string'), path('mid', 'Message id', 'string')] - }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], - action: groups.clearFlagCount - }, - "/members/{uuid}:GET": { - spec: { - path: '/members/{uuid}', - description: "Get a member.", - parameters: [path('uuid', 'Member ID', 'string')] - }, - middleware: [getUserLanguage], - action: members.getMember - }, - "/members/{uuid}/message": { - spec: { - method: 'POST', - description: 'Send a private message to a member', - parameters: [path('uuid', 'The UUID of the member to message', 'string'), body('', '{"message": "The private message to send"}', 'object')] - }, - middleware: [auth.auth], - action: members.sendPrivateMessage - }, - "/members/{uuid}/block": { - spec: { - method: 'POST', - description: 'Block a member from sending private messages', - parameters: [path('uuid', 'The UUID of the member to message', 'string')] - }, - middleware: [auth.auth], - action: user.blockUser - }, - "/members/{uuid}/gift": { - spec: { - method: 'POST', - description: 'Send a gift to a member', - parameters: [path('uuid', 'The UUID of the member', 'string'), body('', '{"type": "gems or subscription", "gems":{"amount":Number, "fromBalance":Boolean}, "subscription":{"months":Number}}', 'object')] - }, - middleware: [auth.auth], - action: members.sendGift - }, - "/hall/heroes": { - spec: {}, - middleware: [auth.auth, getUserLanguage], - action: hall.getHeroes - }, - "/hall/heroes/{uid}:GET": { - spec: { - path: "/hall/heroes/{uid}" - }, - middleware: [auth.auth, getUserLanguage, hall.ensureAdmin], - action: hall.getHero - }, - "/hall/heroes/{uid}:POST": { - spec: { - method: 'POST', - path: "/hall/heroes/{uid}" - }, - middleware: [auth.auth, getUserLanguage, hall.ensureAdmin], - action: hall.updateHero - }, - "/hall/patrons": { - spec: { - parameters: [query('page', 'Page number to fetch (this list is long)', 'string')] - }, - middleware: [auth.auth, getUserLanguage], - action: hall.getPatrons - }, - "/challenges:GET": { - spec: { - path: '/challenges', - description: "Get a list of challenges" - }, - middleware: [auth.auth, getUserLanguage], - action: challenges.list - }, - "/challenges:POST": { - spec: { - path: '/challenges', - method: 'POST', - description: "Create a challenge", - parameters: [body('', 'Challenge object (see ChallengeSchema)', 'object')] - }, - middleware: [auth.auth, getUserLanguage], - action: challenges.create - }, - "/challenges/{cid}:GET": { - spec: { - path: '/challenges/{cid}', - description: 'Get a challenge', - parameters: [path('cid', 'Challenge id', 'string')] - }, - middleware: [auth.auth, getUserLanguage], - action: challenges.get - }, - "/challenges/{cid}/csv": { - spec: { - description: 'Get a challenge (csv format)', - parameters: [path('cid', 'Challenge id', 'string')] - }, - action: challenges.csv - }, - "/challenges/{cid}:POST": { - spec: { - path: '/challenges/{cid}', - method: 'POST', - description: "Update a challenge", - parameters: [path('cid', 'Challenge id', 'string'), body('', 'Challenge object (see ChallengeSchema)', 'object')] - }, - middleware: [auth.auth, getUserLanguage], - action: challenges.update - }, - "/challenges/{cid}:DELETE": { - spec: { - path: '/challenges/{cid}', - method: 'DELETE', - description: "Delete a challenge", - parameters: [path('cid', 'Challenge id', 'string')] - }, - middleware: [auth.auth, getUserLanguage], - action: challenges["delete"] - }, - "/challenges/{cid}/close": { - spec: { - method: 'POST', - description: 'Close a challenge', - parameters: [path('cid', 'Challenge id', 'string'), query('uid', 'User ID of the winner', 'string', true)] - }, - middleware: [auth.auth, getUserLanguage], - action: challenges.selectWinner - }, - "/challenges/{cid}/join": { - spec: { - method: 'POST', - description: "Join a challenge", - parameters: [path('cid', 'Challenge id', 'string')] - }, - middleware: [auth.auth, getUserLanguage], - action: challenges.join - }, - "/challenges/{cid}/leave": { - spec: { - method: 'POST', - description: 'Leave a challenge', - parameters: [path('cid', 'Challenge id', 'string')] - }, - middleware: [auth.auth, getUserLanguage], - action: challenges.leave - }, - "/challenges/{cid}/member/{uid}": { - spec: { - description: "Get a member's progress in a particular challenge", - parameters: [path('cid', 'Challenge id', 'string'), path('uid', 'User id', 'string')] - }, - middleware: [auth.auth, getUserLanguage], - action: challenges.getMember - } - }; - if (nconf.get("NODE_ENV") === "development") { - api["/user/addTenGems"] = { - spec: { - method: 'POST' - }, - action: user.addTenGems - }; - api["/user/addHourglass"] = { - spec: { - method: 'POST' - }, - action: user.addHourglass - }; - }; - - _.each(api, function(route, path) { - var base; - if ((base = route.spec).description == null) { - base.description = ''; - } - _.defaults(route.spec, { - path: path, - nickname: path, - notes: route.spec.description, - summary: route.spec.description, - parameters: [], - errorResponses: [], - method: 'GET' - }); - if (route.middleware == null) { - route.middleware = path.indexOf('/user') === 0 ? [auth.auth, getUserLanguage, cron] : [i18n.getUserLanguage]; - } - swagger["add" + route.spec.method](route); - return true; - }); - - return swagger.configure((nconf.get('BASE_URL')) + "/api/v2", "2"); -}; diff --git a/website/server/routes/api-v2/unsubscription.js b/website/server/routes/api-v2/unsubscription.js deleted file mode 100644 index cbd2e16554..0000000000 --- a/website/server/routes/api-v2/unsubscription.js +++ /dev/null @@ -1,11 +0,0 @@ -var express = require('express'); -var router = express.Router(); -var i18n = require('../../libs/api-v2/i18n'); -var unsubscription = require('../../controllers/api-v2/unsubscription'); -import { - getUserLanguage -} from '../../middlewares/api-v3/language'; - -router.get('/unsubscribe', getUserLanguage, unsubscription.unsubscribe); - -module.exports = router; diff --git a/website/server/routes/payments.js b/website/server/routes/payments.js deleted file mode 100644 index 13e9ec9163..0000000000 --- a/website/server/routes/payments.js +++ /dev/null @@ -1,34 +0,0 @@ -var nconf = require('nconf'); -var express = require('express'); -var router = express.Router(); -var auth = require('../controllers/api-v2/auth'); -var payments = require('../controllers/payments'); -var i18n = require('../libs/api-v2/i18n'); -import { - getUserLanguage -} from '../../middlewares/api-v3/language'; - -router.get('/paypal/checkout', auth.authWithUrl, getUserLanguage, payments.paypalCheckout); -router.get('/paypal/checkout/success', getUserLanguage, payments.paypalCheckoutSuccess); -router.get('/paypal/subscribe', auth.authWithUrl, getUserLanguage, payments.paypalSubscribe); -router.get('/paypal/subscribe/success', getUserLanguage, payments.paypalSubscribeSuccess); -router.get('/paypal/subscribe/cancel', auth.authWithUrl, getUserLanguage, payments.paypalSubscribeCancel); -router.post('/paypal/ipn', getUserLanguage, payments.paypalIPN); // misc ipn handling - -router.post('/stripe/checkout', auth.auth, getUserLanguage, payments.stripeCheckout); -router.post('/stripe/subscribe/edit', auth.auth, getUserLanguage, payments.stripeSubscribeEdit); -//router.get('/stripe/subscribe', auth.authWithUrl, getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead -router.get('/stripe/subscribe/cancel', auth.authWithUrl, getUserLanguage, payments.stripeSubscribeCancel); - -router.post('/amazon/verifyAccessToken', auth.auth, getUserLanguage, payments.amazonVerifyAccessToken); -router.post('/amazon/createOrderReferenceId', auth.auth, getUserLanguage, payments.amazonCreateOrderReferenceId); -router.post('/amazon/checkout', auth.auth, getUserLanguage, payments.amazonCheckout); -router.post('/amazon/subscribe', auth.auth, getUserLanguage, payments.amazonSubscribe); -router.get('/amazon/subscribe/cancel', auth.authWithUrl, getUserLanguage, payments.amazonSubscribeCancel); - -router.post('/iap/android/verify', auth.authWithUrl, /*getUserLanguage, */payments.iapAndroidVerify); -router.post('/iap/ios/verify', auth.auth, /*getUserLanguage, */ payments.iapIosVerify); - -router.get('/api/v2/coupons/valid-discount/:code', /*auth.authWithUrl, getUserLanguage, */ payments.validCoupon); - -module.exports = router; diff --git a/website/server/server.js b/website/server/server.js index 2271552b21..6179b1cb77 100644 --- a/website/server/server.js +++ b/website/server/server.js @@ -1,8 +1,8 @@ import nconf from 'nconf'; -import logger from './libs/api-v3/logger'; +import logger from './libs/logger'; import express from 'express'; import http from 'http'; -import attachMiddlewares from './middlewares/api-v3/index'; +import attachMiddlewares from './middlewares/index'; import Bluebird from 'bluebird'; global.Promise = Bluebird; @@ -13,11 +13,11 @@ const app = express(); app.set('port', nconf.get('PORT')); // Setup translations -import './libs/api-v3/i18n'; +import './libs/i18n'; // Load config files -import './libs/api-v3/setupMongoose'; -import './libs/api-v3/setupPassport'; +import './libs/setupMongoose'; +import './libs/setupPassport'; // Load some schemas & models import './models/challenge'; diff --git a/website/views/shared/footer.jade b/website/views/shared/footer.jade index 41ff2f0a43..1537fefebe 100644 --- a/website/views/shared/footer.jade +++ b/website/views/shared/footer.jade @@ -65,8 +65,6 @@ footer.footer(ng-controller='FooterCtrl') a(target='_blank', href='http://devs.habitica.com')=env.t('devBlog') + ' - The Forge' li a(target='_blank', href='/apidoc')=env.t('APIv3') - li - a(target='_blank', href='/static/api-v2')=env.t('APIv2') li a(target='_blank', href='http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths')=env.t('guidanceForBlacksmiths') .col-sm-3 diff --git a/website/views/static/api-v2.jade b/website/views/static/api-v2.jade deleted file mode 100644 index c6e5c034f0..0000000000 --- a/website/views/static/api-v2.jade +++ /dev/null @@ -1,103 +0,0 @@ -doctype html -html - head - title Swagger UI - link(href='//fonts.googleapis.com/css?family=Droid+Sans:400,700', rel='stylesheet', type='text/css') - link(href='/bower_components/swagger-ui/dist/css/reset.css', media='screen', rel='stylesheet', type='text/css') - link(href='/bower_components/swagger-ui/dist/css/screen.css', media='screen', rel='stylesheet', type='text/css') - link(href='/bower_components/swagger-ui/dist/css/reset.css', media='print', rel='stylesheet', type='text/css') - link(href='/bower_components/swagger-ui/dist/css/screen.css', media='print', rel='stylesheet', type='text/css') - - script(src='/bower_components/swagger-ui/dist/lib/shred.bundle.js', type='text/javascript') - script(src='/bower_components/swagger-ui/dist/lib/jquery-1.8.0.min.js', type='text/javascript') - script(src='/bower_components/swagger-ui/dist/lib/jquery.slideto.min.js', type='text/javascript') - script(src='/bower_components/swagger-ui/dist/lib/jquery.wiggle.min.js', type='text/javascript') - script(src='/bower_components/swagger-ui/dist/lib/jquery.ba-bbq.min.js', type='text/javascript') - script(src='/bower_components/swagger-ui/dist/lib/handlebars-1.0.0.js', type='text/javascript') - script(src='/bower_components/swagger-ui/dist/lib/underscore-min.js', type='text/javascript') - script(src='/bower_components/swagger-ui/dist/lib/backbone-min.js', type='text/javascript') - script(src='/bower_components/swagger-ui/dist/lib/swagger.js', type='text/javascript') - script(src='/bower_components/swagger-ui/dist/swagger-ui.js', type='text/javascript') - script(src='/bower_components/swagger-ui/dist/lib/highlight.7.3.pack.js', type='text/javascript') - script(type='text/javascript'). - $(function () { - window.swaggerUi = new SwaggerUi({ - url: "/api/v2/api-docs", - dom_id: "swagger-ui-container", - supportedSubmitMethods: ['get', 'post', 'put', 'delete'], - onComplete: function(swaggerApi, swaggerUi){ - if(console) { - console.log("Loaded SwaggerUI") - } - $('pre code').each(function(i, e) {hljs.highlightBlock(e)}); - }, - onFailure: function(data) { - if(console) { - console.log("Unable to Load SwaggerUI"); - console.log(data); - } - }, - docExpansion: "none" - }); - - debugger; - - $('#input_apiKey').change(function() { - var key = $('#input_apiKey')[0].value; - console.log("apiKey: " + key); - if(key && key.trim() != "") { - console.log("added key " + key); - window.authorizations.add("apiKey", new ApiKeyAuthorization("x-api-key", key, "header")); - } - }) - $('#input_uuid').change(function() { - var key = $('#input_uuid')[0].value; - console.log("uuid: " + key); - if(key && key.trim() != "") { - console.log("added key " + key); - window.authorizations.add("uuid", new ApiKeyAuthorization("x-api-user", key, "header")); - } - }) - window.swaggerUi.load(); - }); - body.swagger-section - #header - .swagger-ui-wrap - a#logo(href='http://swagger.wordnik.com') Habitica API Documentation - - .swagger-ui-wrap(style='padding:50px') - form#api_selector - .input - input#input_uuid(placeholder='UUID', name='uuid', type='text') - input#input_apiKey(placeholder='API Key', name='apiKey', type='password') - //.input - input#input_baseUrl(placeholder='http://example.com/api', name='baseUrl', type='text') - //.input - a#explore(href='#') Explore - br - h2 API v3 - p This page contains documentation for version 2 of Habitica's API. A new API version, the third, has been released and its documentation can be found here and an introductory blog post with the most important changes here. - p API v2 is still available to give time to developers to port their apps and integration to the new API but it's considered deprecated and should not be used for new projects. It'll be completely retired shortly. - br - h2 Two API Types - p Habitica's API is meant for two different audiences: (1) extensions and scripts, and (2) full-fledged applications. Extensions and scripts can utilize Habitica's up/down scoring for individual tasks. An example of this in action is the Chrome Extension, which up-scores you for visiting productive websites, and down-scores you for visiting procrastination websites. Other examples currently in use are Pomodoro, Anki, and Github scripts - which up-score you for good behavior and downscore you for bad behavior - see the list. The second API consumer is for full-fledge applications, which need read / write access to the entire user document. An example of this would be Mobile Apps or Desktop application. - h2 Extensions / Scripts - p Habitica has a simple API for up-scoring and down-scoring third party Habits: POST /api/v2/user/tasks/{id}/{direction} (headers x-api-user and x-api-key required). - h4 Example - p curl -X POST -H "x-api-key: YOUR_API_TOKEN" -H "x-api-user: YOUR_USER_ID" https://habitica.com/api/v2/user/tasks/productivity/up - p Note: You may need to add --compressed -H "Content-Type:application/json" to your curl if you get errors. - ul - li POST to the URL /api/v2/user/tasks/{id}/{direction} - ul - li {direction} is 'up' or 'down' - li {id} is a unique identifier for a Habit, which you make up, consisting of lowercase letters. Try to make it something common, like 'productivity' or 'fitness' - because other services may piggy-back off your Habit. For example, the Chrome extension down-scores a productivity Habit when you visit vice websites (reddit, 9gag, etc). However, Pomodoro up-scores productivity when you complete a Pomodoro task. So the two services share a single Habit to score your overall productivity. If the Habit doesn't yet exist, it is created the first time you POST to this URL. - li apiToken (POST body) required - p A more advanced version that allows modification of other types of tasks is available in the /user/tasks/{id}/{direction} route in the full API below. - h2 Full API - p All API requests should be prefaced by https://habitica.com. Every authenticated request should include two headers. Your api key (x-api-key) and your user id (x-api-user). Do not include {} braces in your header (-H 'x-api-user: a94b6d9d-6b64-43ae-856c-2c3f211bd426') - h2 Requirements: - p The base-url for all routes is /api/v2. So /user actions will be at https://habitica.com/api/v2/*. You need to send x-api-user and x-api-key headers for each request. - p For create & edit paths (PUT & POST), you'll need to know the schema of the object you're trying to create or edit. See Schema definitions here - p If any of the documentation is lacking or you're having trouble with it, please post an issue to Github - #message-bar.swagger-ui-wrap - #swagger-ui-container.swagger-ui-wrap