diff --git a/Gruntfile.js b/Gruntfile.js index 5b03657165..be40b40816 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -140,7 +140,7 @@ module.exports = function(grunt) { grunt.registerTask('test:prepare:translations', function() { require('coffee-script'); - var i18n = require('./website/src/i18n'), + var i18n = require('./website/src/libs/i18n'), fs = require('fs'); fs.writeFileSync('test/spec/mocks/translations.js', "if(!window.env) window.env = {};\n" + diff --git a/tasks/gulp-console.js b/tasks/gulp-console.js index 5b98853845..95a1962fd4 100644 --- a/tasks/gulp-console.js +++ b/tasks/gulp-console.js @@ -2,9 +2,9 @@ import 'coffee-script'; import mongoose from 'mongoose'; import autoinc from 'mongoose-id-autoinc'; -import logging from '../website/src/logging'; +import logging from '../website/src/libs/logging'; import nconf from 'nconf'; -import utils from '../website/src/utils'; +import utils from '../website/src/libs/utils'; import repl from 'repl'; import gulp from 'gulp'; diff --git a/test/api-legacy/pushNotifications.coffee b/test/api-legacy/pushNotifications.coffee index 3cbac5ab3d..d5df5d51e9 100644 --- a/test/api-legacy/pushNotifications.coffee +++ b/test/api-legacy/pushNotifications.coffee @@ -19,7 +19,7 @@ describe "Push-Notifications", -> done() context "Challenges", -> - challenges = rewire("../../website/src/controllers/challenges") + challenges = rewire("../../website/src/controllers/api-v2/challenges") challenges.__set__('pushNotify', pushSpy) challengeMock = { findById: (arg, cb) -> @@ -65,7 +65,7 @@ describe "Push-Notifications", -> recipient = null - groups = rewire("../../website/src/controllers/groups") + groups = rewire("../../website/src/controllers/api-v2/groups") groups.__set__('pushNotify', pushSpy) before (done) -> @@ -226,7 +226,7 @@ describe "Push-Notifications", -> , false context "sending gems from balance", -> - members = rewire("../../website/src/controllers/members") + members = rewire("../../website/src/controllers/api-v2/members") members.sendMessage = -> true members.__set__('pushNotify', pushSpy) diff --git a/test/common/algos.mocha.coffee b/test/common/algos.mocha.coffee index 642156b752..b413c3e000 100644 --- a/test/common/algos.mocha.coffee +++ b/test/common/algos.mocha.coffee @@ -3,7 +3,7 @@ expect = require 'expect.js' sinon = require 'sinon' moment = require 'moment' shared = require '../../common/script/index.coffee' -shared.i18n.translations = require('../../website/src/i18n.js').translations +shared.i18n.translations = require('../../website/src/libs/i18n.js').translations test_helper = require './test_helper' test_helper.addCustomMatchers() $w = (s)->s.split(' ') diff --git a/test/common/dailies.coffee b/test/common/dailies.coffee index d06c291f27..e5e3fad81a 100644 --- a/test/common/dailies.coffee +++ b/test/common/dailies.coffee @@ -3,7 +3,7 @@ expect = require 'expect.js' sinon = require 'sinon' moment = require 'moment' shared = require '../../common/script/index.coffee' -shared.i18n.translations = require('../../website/src/i18n.js').translations +shared.i18n.translations = require('../../website/src/libs/i18n.js').translations repeatWithoutLastWeekday = ()-> repeat = {su:true,m:true,t:true,w:true,th:true,f:true,s:true} diff --git a/test/common/user.fns.ultimateGear.test.js b/test/common/user.fns.ultimateGear.test.js index b7c091e3f5..d2b82f43e8 100644 --- a/test/common/user.fns.ultimateGear.test.js +++ b/test/common/user.fns.ultimateGear.test.js @@ -1,7 +1,7 @@ 'use strict'; var shared = require('../../common/script/index.coffee'); -shared.i18n.translations = require('../../website/src/i18n.js').translations +shared.i18n.translations = require('../../website/src/libs/i18n.js').translations require('./test_helper'); diff --git a/test/helpers/api.helper.js b/test/helpers/api.helper.js index 4a2084e4ea..f34c0d1384 100644 --- a/test/helpers/api.helper.js +++ b/test/helpers/api.helper.js @@ -9,7 +9,7 @@ import {v4 as generateUUID} from 'uuid'; import superagent from 'superagent'; import i18n from '../../common/script/src/i18n'; require('coffee-script'); -i18n.translations = require('../../website/src/i18n.js').translations; +i18n.translations = require('../../website/src/libs/i18n.js').translations; const API_TEST_SERVER_PORT = 3003; diff --git a/test/helpers/content.helper.js b/test/helpers/content.helper.js index 83178c1a52..c54a62d508 100644 --- a/test/helpers/content.helper.js +++ b/test/helpers/content.helper.js @@ -3,7 +3,7 @@ import {each} from 'lodash'; import i18n from '../../common/script/src/i18n'; require('coffee-script'); -i18n.translations = require('../../website/src/i18n.js').translations; +i18n.translations = require('../../website/src/libs/i18n.js').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/server_side/analytics.test.js b/test/server_side/analytics.test.js index b88d24c364..7cde7aede8 100644 --- a/test/server_side/analytics.test.js +++ b/test/server_side/analytics.test.js @@ -30,7 +30,7 @@ describe('analytics', function() { }); describe('init', function() { - var analytics = rewire('../../website/src/analytics'); + var analytics = rewire('../../website/src/libs/analytics'); it('throws an error if no options are passed in', function() { expect(analytics).to.throw('No options provided'); @@ -62,7 +62,7 @@ describe('analytics', function() { describe('track', function() { var analyticsData, event_type; - var analytics = rewire('../../website/src/analytics'); + var analytics = rewire('../../website/src/libs/analytics'); var initializedAnalytics; beforeEach(function() { @@ -370,7 +370,7 @@ describe('analytics', function() { var purchaseData; - var analytics = rewire('../../website/src/analytics'); + var analytics = rewire('../../website/src/libs/analytics'); var initializedAnalytics; beforeEach(function() { diff --git a/test/server_side/controllers/groups.test.js b/test/server_side/controllers/groups.test.js index ad9196e13f..9e970c4afc 100644 --- a/test/server_side/controllers/groups.test.js +++ b/test/server_side/controllers/groups.test.js @@ -5,10 +5,10 @@ var expect = chai.expect; var Q = require('q'); var Group = require('../../../website/src/models/group').model; -var groupsController = require('../../../website/src/controllers/groups'); +var groupsController = require('../../../website/src/controllers/api-v2/groups'); describe('Groups Controller', function() { - var utils = require('../../../website/src/utils'); + var utils = require('../../../website/src/libs/utils'); describe('#invite', function() { var res, req, user, group; diff --git a/test/server_side/controllers/user.test.js b/test/server_side/controllers/user.test.js index aac7bf69b7..a000c0cde1 100644 --- a/test/server_side/controllers/user.test.js +++ b/test/server_side/controllers/user.test.js @@ -4,7 +4,7 @@ chai.use(require("sinon-chai")) var expect = chai.expect var rewire = require('rewire'); -var userController = rewire('../../../website/src/controllers/user'); +var userController = rewire('../../../website/src/controllers/api-v2/user'); describe('User Controller', function() { @@ -359,7 +359,7 @@ describe('User Controller', function() { }); it('sends webhooks', function() { - var webhook = require('../../../website/src/webhook'); + var webhook = require('../../../website/src/libs/webhook'); sinon.spy(webhook, 'sendTaskWebhook'); userController.score(req, res); diff --git a/test/server_side/webhooks.test.js b/test/server_side/webhooks.test.js index 596e441620..ef6636bf93 100644 --- a/test/server_side/webhooks.test.js +++ b/test/server_side/webhooks.test.js @@ -4,7 +4,7 @@ chai.use(require("sinon-chai")) var expect = chai.expect var rewire = require('rewire'); -var webhook = rewire('../../website/src/webhook'); +var webhook = rewire('../../website/src/libs/webhook'); describe('webhooks', function() { var postSpy; diff --git a/website/src/controllers/auth.js b/website/src/controllers/api-v2/auth.js similarity index 98% rename from website/src/controllers/auth.js rename to website/src/controllers/api-v2/auth.js index 7fa551689e..7f6ae19bd3 100644 --- a/website/src/controllers/auth.js +++ b/website/src/controllers/api-v2/auth.js @@ -1,16 +1,16 @@ var _ = require('lodash'); var validator = require('validator'); var passport = require('passport'); -var shared = require('../../../common'); +var shared = require('../../../../common'); var async = require('async'); -var utils = require('../utils'); +var utils = require('../../libs/utils'); var nconf = require('nconf'); var request = require('request'); var FirebaseTokenGenerator = require('firebase-token-generator'); -var User = require('../models/user').model; -var EmailUnsubscription = require('../models/emailUnsubscription').model; +var User = require('../../models/user').model; +var EmailUnsubscription = require('../../models/emailUnsubscription').model; var analytics = utils.analytics; -var i18n = require('./../i18n'); +var i18n = require('./../../libs/i18n'); var isProd = nconf.get('NODE_ENV') === 'production'; diff --git a/website/src/controllers/challenges.js b/website/src/controllers/api-v2/challenges.js similarity index 97% rename from website/src/controllers/challenges.js rename to website/src/controllers/api-v2/challenges.js index 6ac40b2571..72f28d9f3d 100644 --- a/website/src/controllers/challenges.js +++ b/website/src/controllers/api-v2/challenges.js @@ -3,15 +3,15 @@ var _ = require('lodash'); var nconf = require('nconf'); var async = require('async'); -var shared = require('../../../common'); -var User = require('./../models/user').model; -var Group = require('./../models/group').model; -var Challenge = require('./../models/challenge').model; -var logging = require('./../logging'); +var shared = require('../../../../common'); +var User = require('./../../models/user').model; +var Group = require('./../../models/group').model; +var Challenge = require('./../../models/challenge').model; +var logging = require('./../../libs/logging'); var csv = require('express-csv'); -var utils = require('../utils'); +var utils = require('../../libs/utils'); var api = module.exports; -var pushNotify = require('./pushNotifications'); +var pushNotify = require('./../pushNotifications'); /* ------------------------------------------------------------------------ diff --git a/website/src/controllers/coupon.js b/website/src/controllers/api-v2/coupon.js similarity index 95% rename from website/src/controllers/coupon.js rename to website/src/controllers/api-v2/coupon.js index b8450d34f3..c6811037e5 100644 --- a/website/src/controllers/coupon.js +++ b/website/src/controllers/api-v2/coupon.js @@ -1,5 +1,5 @@ var _ = require('lodash'); -var Coupon = require('./../models/coupon').model; +var Coupon = require('./../../models/coupon').model; var api = module.exports; var csv = require('express-csv'); var async = require('async'); diff --git a/website/src/controllers/groups.js b/website/src/controllers/api-v2/groups.js similarity index 98% rename from website/src/controllers/groups.js rename to website/src/controllers/api-v2/groups.js index 1e274ad565..88f968d219 100644 --- a/website/src/controllers/groups.js +++ b/website/src/controllers/api-v2/groups.js @@ -9,17 +9,17 @@ var _ = require('lodash'); var nconf = require('nconf'); var async = require('async'); var Q = require('q'); -var utils = require('./../utils'); -var shared = require('../../../common'); -var User = require('./../models/user').model; -var Group = require('./../models/group').model; -var Challenge = require('./../models/challenge').model; -var EmailUnsubscription = require('./../models/emailUnsubscription').model; +var utils = require('./../../libs/utils'); +var shared = require('../../../../common'); +var User = require('./../../models/user').model; +var Group = require('./../../models/group').model; +var Challenge = require('./../../models/challenge').model; +var EmailUnsubscription = require('./../../models/emailUnsubscription').model; var isProd = nconf.get('NODE_ENV') === 'production'; var api = module.exports; -var pushNotify = require('./pushNotifications'); +var pushNotify = require('./../pushNotifications'); var analytics = utils.analytics; -var firebase = require('../libs/firebase'); +var firebase = require('../../libs/firebase'); /* ------------------------------------------------------------------------ diff --git a/website/src/controllers/hall.js b/website/src/controllers/api-v2/hall.js similarity index 95% rename from website/src/controllers/hall.js rename to website/src/controllers/api-v2/hall.js index 8754bc2e94..f3a3ead240 100644 --- a/website/src/controllers/hall.js +++ b/website/src/controllers/api-v2/hall.js @@ -1,9 +1,9 @@ var _ = require('lodash'); var nconf = require('nconf'); var async = require('async'); -var shared = require('../../../common'); -var User = require('./../models/user').model; -var Group = require('./../models/group').model; +var shared = require('../../../../common'); +var User = require('./../../models/user').model; +var Group = require('./../../models/group').model; var api = module.exports; api.ensureAdmin = function(req, res, next) { diff --git a/website/src/controllers/members.js b/website/src/controllers/api-v2/members.js similarity index 95% rename from website/src/controllers/members.js rename to website/src/controllers/api-v2/members.js index c7fe341d3d..c528b92cd7 100644 --- a/website/src/controllers/members.js +++ b/website/src/controllers/api-v2/members.js @@ -1,13 +1,13 @@ var User = require('mongoose').model('User'); -var groups = require('../models/group'); +var groups = require('../../models/group'); var partyFields = require('./groups').partyFields var api = module.exports; var async = require('async'); var _ = require('lodash'); -var shared = require('../../../common'); -var utils = require('../utils'); +var shared = require('../../../../common'); +var utils = require('../../libs/utils'); var nconf = require('nconf'); -var pushNotify = require('./pushNotifications'); +var pushNotify = require('./../pushNotifications'); var fetchMember = function(uuid, restrict){ return function(cb){ diff --git a/website/src/controllers/unsubscription.js b/website/src/controllers/api-v2/unsubscription.js similarity index 84% rename from website/src/controllers/unsubscription.js rename to website/src/controllers/api-v2/unsubscription.js index 8c0edffd5f..f768236ee5 100644 --- a/website/src/controllers/unsubscription.js +++ b/website/src/controllers/api-v2/unsubscription.js @@ -1,7 +1,7 @@ -var User = require('../models/user').model; -var EmailUnsubscription = require('../models/emailUnsubscription').model; -var utils = require('../utils'); -var i18n = require('../../../common').i18n; +var User = require('../../models/user').model; +var EmailUnsubscription = require('../../models/emailUnsubscription').model; +var utils = require('../../libs/utils'); +var i18n = require('../../../../common').i18n; var api = module.exports = {}; diff --git a/website/src/controllers/user.js b/website/src/controllers/api-v2/user.js similarity index 97% rename from website/src/controllers/user.js rename to website/src/controllers/api-v2/user.js index b9559e23c5..caf3c092a4 100644 --- a/website/src/controllers/user.js +++ b/website/src/controllers/api-v2/user.js @@ -5,19 +5,19 @@ var ipn = require('paypal-ipn'); var _ = require('lodash'); var nconf = require('nconf'); var async = require('async'); -var shared = require('../../../common'); -var User = require('./../models/user').model; -var utils = require('./../utils'); +var shared = require('../../../../common'); +var User = require('./../../models/user').model; +var utils = require('./../../libs/utils'); var analytics = utils.analytics; -var Group = require('./../models/group').model; -var Challenge = require('./../models/challenge').model; +var Group = require('./../../models/group').model; +var Challenge = require('./../../models/challenge').model; var moment = require('moment'); -var logging = require('./../logging'); +var logging = require('./../../libs/logging'); var acceptablePUTPaths; var api = module.exports; var qs = require('qs'); -var firebase = require('../libs/firebase'); -var webhook = require('../webhook'); +var firebase = require('../../libs/firebase'); +var webhook = require('../../libs/webhook'); // api.purchase // Shared.ops @@ -290,7 +290,7 @@ api.getUserAnonymized = function(req, res, next) { * The trick here is to only accept leaf paths, not root/intermediate paths (see http://goo.gl/OEzkAs) * FIXME - 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, function(m,v,leaf){ +acceptablePUTPaths = _.reduce(require('./../../models/user').schema.paths, function(m,v,leaf){ var found= _.find('achievements filters flags invitations lastCron party preferences profile stats inbox'.split(' '), function(root){ return leaf.indexOf(root) == 0; }); diff --git a/website/src/controllers/payments/index.js b/website/src/controllers/payments/index.js index 1f8935645d..fd7f98a9c2 100644 --- a/website/src/controllers/payments/index.js +++ b/website/src/controllers/payments/index.js @@ -2,13 +2,13 @@ var _ = require('lodash'); var shared = require('../../../../common'); var nconf = require('nconf'); -var utils = require('./../../utils'); +var utils = require('./../../libs/utils'); var moment = require('moment'); var isProduction = nconf.get("NODE_ENV") === "production"; var stripe = require('./stripe'); var paypal = require('./paypal'); var amazon = require('./amazon'); -var members = require('../members') +var members = require('../api-v2/members') var async = require('async'); var iap = require('./iap'); var mongoose= require('mongoose'); diff --git a/website/src/controllers/payments/paypal.js b/website/src/controllers/payments/paypal.js index 094171e3af..30970f72cf 100644 --- a/website/src/controllers/payments/paypal.js +++ b/website/src/controllers/payments/paypal.js @@ -5,7 +5,7 @@ var _ = require('lodash'); var url = require('url'); var User = require('mongoose').model('User'); var payments = require('./index'); -var logger = require('../../logging'); +var logger = require('../../libs/logging'); var ipn = require('paypal-ipn'); var paypal = require('paypal-rest-sdk'); var shared = require('../../../../common'); diff --git a/website/src/analytics.js b/website/src/libs/analytics.js similarity index 98% rename from website/src/analytics.js rename to website/src/libs/analytics.js index 19a8a9b14c..769a5cf8e1 100644 --- a/website/src/analytics.js +++ b/website/src/libs/analytics.js @@ -2,7 +2,7 @@ require('coffee-script'); require('./i18n'); var _ = require('lodash'); -var Content = require('../../common').content; +var Content = require('../../../common').content; var Amplitude = require('amplitude'); var googleAnalytics = require('universal-analytics'); diff --git a/website/src/i18n.js b/website/src/libs/i18n.js similarity index 94% rename from website/src/i18n.js rename to website/src/libs/i18n.js index 463565ac3e..f2170b453c 100644 --- a/website/src/i18n.js +++ b/website/src/libs/i18n.js @@ -1,11 +1,11 @@ var fs = require('fs'), path = require('path'), _ = require('lodash'), - User = require('./models/user').model, - shared = require('../../common'), + User = require('../models/user').model, + shared = require('../../../common'), translations = {}; -var localePath = path.join(__dirname, "/../../common/locales/") +var localePath = path.join(__dirname, "/../../../common/locales/") var loadTranslations = function(locale){ var files = fs.readdirSync(path.join(localePath, locale)); @@ -54,7 +54,7 @@ _.each(langCodes, function(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'); + var f = fs.readFileSync(path.join(__dirname, '/../../../node_modules/moment/locale/' + lang.momentLangCode + '.js'), 'utf8'); momentLangs[code] = f; }catch (e){} }); diff --git a/website/src/logging.js b/website/src/libs/logging.js similarity index 100% rename from website/src/logging.js rename to website/src/libs/logging.js diff --git a/website/src/utils.js b/website/src/libs/utils.js similarity index 98% rename from website/src/utils.js rename to website/src/libs/utils.js index d63c54b531..1b1231e102 100644 --- a/website/src/utils.js +++ b/website/src/libs/utils.js @@ -170,7 +170,7 @@ module.exports.setupConfig = function(){ nconf.argv() .env() //.file('defaults', path.join(path.resolve(__dirname, '../config.json.example'))) - .file('user', path.join(path.resolve(__dirname, './../../config.json'))); + .file('user', path.join(path.resolve(__dirname, './../../../config.json'))); if (nconf.get('NODE_ENV') === "development") Error.stackTraceLimit = Infinity; diff --git a/website/src/webhook.js b/website/src/libs/webhook.js similarity index 100% rename from website/src/webhook.js rename to website/src/libs/webhook.js diff --git a/website/src/middlewares/errorHandler.js b/website/src/middlewares/errorHandler.js index eebf25ac6b..9a82316a75 100644 --- a/website/src/middlewares/errorHandler.js +++ b/website/src/middlewares/errorHandler.js @@ -1,4 +1,4 @@ -var logging = require('../logging'); +var logging = require('../libs/logging'); module.exports = function(err, req, res, next) { //res.locals.domain.emit('error', err); diff --git a/website/src/middlewares/locals.js b/website/src/middlewares/locals.js index e9dbb31260..2f238a6a99 100644 --- a/website/src/middlewares/locals.js +++ b/website/src/middlewares/locals.js @@ -1,8 +1,8 @@ var nconf = require('nconf'); var _ = require('lodash'); -var utils = require('../utils'); +var utils = require('../libs/utils'); var shared = require('../../../common'); -var i18n = require('../i18n.js'); +var i18n = require('../libs/i18n'); var buildManifest = require('../libs/buildManifest'); var shared = require('../../../common'); var forceRefresh = require('./forceRefresh'); diff --git a/website/src/models/group.js b/website/src/models/group.js index 9ec1f28a21..17817994c1 100644 --- a/website/src/models/group.js +++ b/website/src/models/group.js @@ -4,7 +4,7 @@ var User = require('./user').model; var shared = require('../../../common'); var _ = require('lodash'); var async = require('async'); -var logging = require('../logging'); +var logging = require('../libs/logging'); var Challenge = require('./../models/challenge').model; var firebase = require('../libs/firebase'); diff --git a/website/src/routes/apiv1.js b/website/src/routes/api-v1.js similarity index 96% rename from website/src/routes/apiv1.js rename to website/src/routes/api-v1.js index f2876fedc4..1002391cae 100644 --- a/website/src/routes/apiv1.js +++ b/website/src/routes/api-v1.js @@ -3,10 +3,10 @@ var router = new express.Router(); var _ = require('lodash'); var async = require('async'); var icalendar = require('icalendar'); -var api = require('./../controllers/user'); -var auth = require('./../controllers/auth'); -var logging = require('./../logging'); -var i18n = require('./../i18n'); +var api = require('./../controllers/api-v2/user'); +var auth = require('./../controllers/api-v2/auth'); +var logging = require('./../libs/logging'); +var i18n = require('./../libs/i18n'); var forceRefresh = require('../middlewares/forceRefresh').middleware; /* ---------- Deprecated API ------------*/ @@ -170,4 +170,4 @@ router.get('*', i18n.getUserLanguage, deprecated); router.post('*', i18n.getUserLanguage, deprecated); router.put('*', i18n.getUserLanguage, deprecated); -module.exports = router; +module.exports = router; \ No newline at end of file diff --git a/website/src/routes/auth.js b/website/src/routes/api-v2/auth.js similarity index 89% rename from website/src/routes/auth.js rename to website/src/routes/api-v2/auth.js index 7d53253210..39f2f6e069 100644 --- a/website/src/routes/auth.js +++ b/website/src/routes/api-v2/auth.js @@ -1,6 +1,6 @@ -var auth = require('../controllers/auth'); +var auth = require('../../controllers/api-v2/auth'); var express = require('express'); -var i18n = require('../i18n'); +var i18n = require('../../libs/i18n'); var router = new express.Router(); /* auth.auth*/ @@ -15,8 +15,7 @@ router.post('/api/v2/user/change-username', i18n.getUserLanguage, auth.auth, aut router.post('/api/v2/user/change-email', i18n.getUserLanguage, auth.auth, auth.changeEmail); router.post('/api/v2/user/auth/firebase', i18n.getUserLanguage, auth.auth, auth.getFirebaseToken); -router.post('/api/v1/register', i18n.getUserLanguage, auth.registerUser); -router.post('/api/v1/user/auth/local', i18n.getUserLanguage, auth.loginLocal); -router.post('/api/v1/user/auth/social', i18n.getUserLanguage, auth.loginSocial); - +router.post('/api/v1/register', i18n.getUserLanguage, auth.registerUser); +router.post('/api/v1/user/auth/local', i18n.getUserLanguage, auth.loginLocal); +router.post('/api/v1/user/auth/social', i18n.getUserLanguage, auth.loginSocial); module.exports = router; \ No newline at end of file diff --git a/website/src/routes/coupon.js b/website/src/routes/api-v2/coupon.js similarity index 75% rename from website/src/routes/coupon.js rename to website/src/routes/api-v2/coupon.js index 57a33866c6..811d81a6f2 100644 --- a/website/src/routes/coupon.js +++ b/website/src/routes/api-v2/coupon.js @@ -1,9 +1,9 @@ var nconf = require('nconf'); var express = require('express'); var router = new express.Router(); -var auth = require('../controllers/auth'); -var coupon = require('../controllers/coupon'); -var i18n = require('../i18n'); +var auth = require('../../controllers/api-v2/auth'); +var coupon = require('../../controllers/api-v2/coupon'); +var i18n = require('../../libs/i18n'); router.get('/api/v2/coupons', auth.authWithUrl, i18n.getUserLanguage, coupon.ensureAdmin, coupon.getCoupons); router.post('/api/v2/coupons/generate/:event', auth.auth, i18n.getUserLanguage, coupon.ensureAdmin, coupon.generateCoupons); diff --git a/website/src/routes/api-v2/swagger.js b/website/src/routes/api-v2/swagger.js new file mode 100644 index 0000000000..d93cfe6271 --- /dev/null +++ b/website/src/routes/api-v2/swagger.js @@ -0,0 +1,777 @@ +/* +---------- /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.coffee#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: +$ mocha test/user.mocha.coffee + */ + +require('coffee-script'); +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/dataexport"); +var nconf = require("nconf"); +var cron = user.cron; +var _ = require('lodash'); +var content = require('../../../../common').content; +var i18n = require('../../libs/i18n'); +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, i18n.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, i18n.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, i18n.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.coffee)", '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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.getUserLanguage, groups.attachGroup], + action: groups.clearFlagCount + }, + "/members/{uuid}:GET": { + spec: { + path: '/members/{uuid}', + description: "Get a member.", + parameters: [path('uuid', 'Member ID', 'string')] + }, + middleware: [i18n.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, i18n.getUserLanguage], + action: hall.getHeroes + }, + "/hall/heroes/{uid}:GET": { + spec: { + path: "/hall/heroes/{uid}" + }, + middleware: [auth.auth, i18n.getUserLanguage, hall.ensureAdmin], + action: hall.getHero + }, + "/hall/heroes/{uid}:POST": { + spec: { + method: 'POST', + path: "/hall/heroes/{uid}" + }, + middleware: [auth.auth, i18n.getUserLanguage, hall.ensureAdmin], + action: hall.updateHero + }, + "/hall/patrons": { + spec: { + parameters: [query('page', 'Page number to fetch (this list is long)', 'string')] + }, + middleware: [auth.auth, i18n.getUserLanguage], + action: hall.getPatrons + }, + "/challenges:GET": { + spec: { + path: '/challenges', + description: "Get a list of challenges" + }, + middleware: [auth.auth, i18n.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, i18n.getUserLanguage], + action: challenges.create + }, + "/challenges/{cid}:GET": { + spec: { + path: '/challenges/{cid}', + description: 'Get a challenge', + parameters: [path('cid', 'Challenge id', 'string')] + }, + middleware: [auth.auth, i18n.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, i18n.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, i18n.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, i18n.getUserLanguage], + action: challenges.selectWinner + }, + "/challenges/{cid}/join": { + spec: { + method: 'POST', + description: "Join a challenge", + parameters: [path('cid', 'Challenge id', 'string')] + }, + middleware: [auth.auth, i18n.getUserLanguage], + action: challenges.join + }, + "/challenges/{cid}/leave": { + spec: { + method: 'POST', + description: 'Leave a challenge', + parameters: [path('cid', 'Challenge id', 'string')] + }, + middleware: [auth.auth, i18n.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, i18n.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, i18n.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/src/routes/unsubscription.js b/website/src/routes/api-v2/unsubscription.js similarity index 60% rename from website/src/routes/unsubscription.js rename to website/src/routes/api-v2/unsubscription.js index efd06f9197..942a396eef 100644 --- a/website/src/routes/unsubscription.js +++ b/website/src/routes/api-v2/unsubscription.js @@ -1,7 +1,7 @@ var express = require('express'); var router = new express.Router(); -var i18n = require('../i18n'); -var unsubscription = require('../controllers/unsubscription'); +var i18n = require('../../libs/i18n'); +var unsubscription = require('../../controllers/api-v2/unsubscription'); router.get('/unsubscribe', i18n.getUserLanguage, unsubscription.unsubscribe); diff --git a/website/src/routes/apiv2.coffee b/website/src/routes/apiv2.coffee deleted file mode 100644 index 746423684f..0000000000 --- a/website/src/routes/apiv2.coffee +++ /dev/null @@ -1,853 +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.coffee#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: -$ mocha test/user.mocha.coffee -### - -user = require("../controllers/user") -groups = require("../controllers/groups") -members = require("../controllers/members") -auth = require("../controllers/auth") -hall = require("../controllers/hall") -challenges = require("../controllers/challenges") -dataexport = require("../controllers/dataexport") -nconf = require("nconf") -cron = user.cron -_ = require('lodash') -content = require('../../../common').content -i18n = require('../i18n') -forceRefresh = require('../middlewares/forceRefresh').middleware - -module.exports = (swagger, v2) -> - [path,body,query] = [swagger.pathParam, swagger.bodyParam, swagger.queryParam] - - swagger.setAppHandler(v2) - swagger.setErrorHandler("next") - swagger.setHeaders = -> #disable setHeaders, since we have our own thing going on in middleware.js (and which requires `req`, which swagger doesn't pass in) - swagger.configureSwaggerPaths("", "/api-docs", "") - - api = - - '/status': - spec: - description: "Returns the status of the server (up or down). Does not require authentication." - action: (req, res) -> - 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, i18n.getUserLanguage] - action: dataexport.history #[todo] encode data output options in the data controller and use these to build routes - - # --------------------------------- - # User - # --------------------------------- - - # Scoring - - "/user/tasks/{id}/{direction}": - spec: - #notes: "Simple scoring of a task." - 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 - - # Tasks - "/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, i18n.getUserLanguage] ## removing cron since they may want to remove task first - action: challenges.unlink - - - # Inventory - "/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: [ - #TODO verify these are the correct types - 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 - "/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, i18n.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.coffee)",'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, i18n.getUserLanguage, cron, user.sessionPartyInvite] - action: user.batchUpdate - - # Tags - "/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 - - # Webhooks - "/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 - - # Push Notifications - "/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 - # --------------------------------- - "/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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.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, i18n.getUserLanguage, groups.attachGroup] - action: groups.questLeave - - #TODO PUT /groups/:gid/chat/:messageId - - "/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, i18n.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, i18n.getUserLanguage, groups.attachGroup] - action: groups.postChat - - # placing before route below, so that if !=='seen' it goes to next() - "/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, i18n.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, i18n.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, i18n.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, i18n.getUserLanguage, groups.attachGroup] - action: groups.clearFlagCount - - # --------------------------------- - # Members - # --------------------------------- - "/members/{uuid}:GET": - spec: - path: '/members/{uuid}' - description: "Get a member." - parameters: [path('uuid','Member ID','string')] - middleware: [i18n.getUserLanguage] # removed auth.auth, so anon users can view shared avatars - 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 of Heroes / Patrons - # --------------------------------- - "/hall/heroes": - spec: {} - middleware:[auth.auth, i18n.getUserLanguage] - action: hall.getHeroes - - "/hall/heroes/{uid}:GET": - spec: path: "/hall/heroes/{uid}" - middleware:[auth.auth, i18n.getUserLanguage, hall.ensureAdmin] - action: hall.getHero - - "/hall/heroes/{uid}:POST": - spec: - method: 'POST' - path: "/hall/heroes/{uid}" - middleware: [auth.auth, i18n.getUserLanguage, hall.ensureAdmin] - action: hall.updateHero - - "/hall/patrons": - spec: - parameters: [ - query 'page','Page number to fetch (this list is long)','string' - ] - middleware:[auth.auth, i18n.getUserLanguage] - action: hall.getPatrons - - - # --------------------------------- - # Challenges - # --------------------------------- - - # Note: while challenges belong to groups, and would therefore make sense as a nested resource - # (eg /groups/:gid/challenges/:cid), they will also be referenced by users from the "challenges" tab - # without knowing which group they belong to. So to prevent unecessary lookups, we have them as a top-level resource - "/challenges:GET": - spec: - path: '/challenges' - description: "Get a list of challenges" - middleware: [auth.auth, i18n.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, i18n.getUserLanguage] - action: challenges.create - - "/challenges/{cid}:GET": - spec: - path: '/challenges/{cid}' - description: 'Get a challenge' - parameters: [path('cid','Challenge id','string')] - middleware: [auth.auth, i18n.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, i18n.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, i18n.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, i18n.getUserLanguage] - action: challenges.selectWinner - - "/challenges/{cid}/join": - spec: - method: 'POST' - description: "Join a challenge" - parameters: [path('cid','Challenge id','string')] - middleware: [auth.auth, i18n.getUserLanguage] - action: challenges.join - - "/challenges/{cid}/leave": - spec: - method: 'POST' - description: 'Leave a challenge' - parameters: [path('cid','Challenge id','string')] - middleware: [auth.auth, i18n.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, i18n.getUserLanguage] - action: challenges.getMember - - - if nconf.get("NODE_ENV") is "development" - api["/user/addTenGems"] = - spec: method:'POST' - action: user.addTenGems - - api["/user/addHourglass"] = - spec: method:'POST' - action: user.addHourglass - - _.each api, (route, path) -> - ## Spec format is: - # spec: - # path: "/pet/{petId}" - # description: "Operations about pets" - # notes: "Returns a pet based on ID" - # summary: "Find pet by ID" - # method: "GET" - # parameters: [path("petId", "ID of pet that needs to be fetched", "string")] - # type: "Pet" - # errorResponses: [swagger.errors.invalid("id"), swagger.errors.notFound("pet")] - # nickname: "getPetById" - - route.spec.description ?= '' - _.defaults route.spec, - path: path - nickname: path - notes: route.spec.description - summary: route.spec.description - parameters: [] - #type: 'Pet' - errorResponses: [] - method: 'GET' - route.middleware ?= if path.indexOf('/user') is 0 then [auth.auth, i18n.getUserLanguage, cron] else [i18n.getUserLanguage] - swagger["add#{route.spec.method}"](route);true - - - swagger.configure("#{nconf.get('BASE_URL')}/api/v2", "2") diff --git a/website/src/routes/dataexport.js b/website/src/routes/dataexport.js index 9fc2ded323..5bf02a228c 100644 --- a/website/src/routes/dataexport.js +++ b/website/src/routes/dataexport.js @@ -1,9 +1,9 @@ var express = require('express'); var router = new express.Router(); var dataexport = require('../controllers/dataexport'); -var auth = require('../controllers/auth'); +var auth = require('../controllers/api-v2/auth'); var nconf = require('nconf'); -var i18n = require('../i18n'); +var i18n = require('../libs/i18n'); var locals = require('../middlewares/locals'); /* Data export */ diff --git a/website/src/routes/pages.js b/website/src/routes/pages.js index be2d429e59..b88393301b 100644 --- a/website/src/routes/pages.js +++ b/website/src/routes/pages.js @@ -3,9 +3,7 @@ var express = require('express'); var router = new express.Router(); var _ = require('lodash'); var locals = require('../middlewares/locals'); -var user = require('../controllers/user'); -var auth = require('../controllers/auth'); -var i18n = require('../i18n'); +var i18n = require('../libs/i18n'); // -------- App -------- router.get('/', i18n.getUserLanguage, locals, function(req, res) { diff --git a/website/src/routes/payments.js b/website/src/routes/payments.js index 01667fe747..41c03210be 100644 --- a/website/src/routes/payments.js +++ b/website/src/routes/payments.js @@ -1,9 +1,9 @@ var nconf = require('nconf'); var express = require('express'); var router = new express.Router(); -var auth = require('../controllers/auth'); +var auth = require('../controllers/api-v2/auth'); var payments = require('../controllers/payments'); -var i18n = require('../i18n'); +var i18n = require('../libs/i18n'); router.get('/paypal/checkout', auth.authWithUrl, i18n.getUserLanguage, payments.paypalCheckout); router.get('/paypal/checkout/success', i18n.getUserLanguage, payments.paypalCheckoutSuccess); diff --git a/website/src/seed.js b/website/src/seed.js deleted file mode 100644 index c54c715168..0000000000 --- a/website/src/seed.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This script is no longer required due to this code in src/models/group.js: - * // initialize tavern if !exists (fresh installs) - * Group.count({_id:'habitrpg'},function(err,ct){ - * ... - * }) - * - * However we're keeping this script in case future seed updates are needed. - * - * Reference: https://github.com/HabitRPG/habitrpg/issues/3852#issuecomment-55334572 - */ - - -/* - -require('coffee-script') // for habitrpg-shared -var nconf = require('nconf'); -var utils = require('./utils'); -var logging = require('./logging'); -utils.setupConfig(); -var async = require('async'); -var mongoose = require('mongoose'); -User = require('./models/user').model; -Group = require('./models/group').model; - -async.waterfall([ - function(cb){ - mongoose.connect(nconf.get('NODE_DB_URI'), cb); - }, - function(cb){ - Group.findById('habitrpg', cb); - }, - function(tavern, cb){ - logging.info({tavern:tavern,cb:cb}); - if (!tavern) { - tavern = new Group({ - _id: 'habitrpg', - chat: [], - leader: '9', - name: 'HabitRPG', - type: 'guild', - privacy:'public' - }); - tavern.save(cb) - } else { - cb(); - } - } -],function(err){ - if (err) throw err; - logging.info("Done initializing database"); - mongoose.disconnect(); -}) - -*/ diff --git a/website/src/server.js b/website/src/server.js index 23ce277eba..94bffe592b 100644 --- a/website/src/server.js +++ b/website/src/server.js @@ -2,9 +2,9 @@ var cluster = require("cluster"); var _ = require('lodash'); var nconf = require('nconf'); -var utils = require('./utils'); +var utils = require('./libs/utils'); utils.setupConfig(); -var logging = require('./logging'); +var logging = require('./libs/logging'); var isProd = nconf.get('NODE_ENV') === 'production'; var isDev = nconf.get('NODE_ENV') === 'development'; var DISABLE_LOGGING = nconf.get('DISABLE_REQUEST_LOGGING'); @@ -29,7 +29,7 @@ if (cores!==0 && cluster.isMaster && (isDev || isProd)) { var shared = require('../../common'); // Setup translations - var i18n = require('./i18n'); + var i18n = require('./libs/i18n'); var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14; var app = express(); @@ -90,54 +90,68 @@ if (cores!==0 && cluster.isMaster && (isDev || isProd)) { var publicDir = path.join(__dirname, "/../public"); app.set("port", nconf.get('PORT')); - require('./middlewares/apiThrottle')(app); - app.use(require('./middlewares/domain')(server,mongoose)); - if (!isProd && !DISABLE_LOGGING) app.use(express.logger("dev")); - app.use(express.compress()); - app.set("views", __dirname + "/../views"); - app.set("view engine", "jade"); - app.use(express.favicon(publicDir + '/favicon.ico')); - app.use(require('./middlewares/cors')); + + // Setup two different Express apps, one that matches everything except '/api/v3' + // and the other for /api/v3 routes, so we can keep the old an new api versions completely separate + // not sharing a single middleware if we don't want to + var oldApp = express(); // api v1 and v2, and not scoped routes + var newApp = express(); // api v3 + + // Route requests to the right app + app.use(app.router); + // Matches all request except the ones going to /api/v3/** + app.all(/^(?!\/api\/v3).+/i, oldApp); + // Matches all requests going to /api/v3 + app.all('/api/v3', newApp); + + require('./middlewares/apiThrottle')(oldApp); + oldApp.use(require('./middlewares/domain')(server,mongoose)); + if (!isProd && !DISABLE_LOGGING) oldApp.use(express.logger("dev")); + oldApp.use(express.compress()); + oldApp.set("views", __dirname + "/../views"); + oldApp.set("view engine", "jade"); + oldApp.use(express.favicon(publicDir + '/favicon.ico')); + oldApp.use(require('./middlewares/cors')); var redirects = require('./middlewares/redirects'); - app.use(redirects.forceHabitica); - app.use(redirects.forceSSL); - app.use(express.urlencoded()); - app.use(express.json()); - app.use(require('method-override')()); - //app.use(express.cookieParser(nconf.get('SESSION_SECRET'))); - app.use(express.cookieParser()); - app.use(express.cookieSession({ secret: nconf.get('SESSION_SECRET'), httpOnly: false, cookie: { maxAge: TWO_WEEKS }})); - //app.use(express.session()); + oldApp.use(redirects.forceHabitica); + oldApp.use(redirects.forceSSL); + oldApp.use(express.urlencoded()); + oldApp.use(express.json()); + oldApp.use(require('method-override')()); + //oldApp.use(express.cookieParser(nconf.get('SESSION_SECRET'))); + oldApp.use(express.cookieParser()); + oldApp.use(express.cookieSession({ secret: nconf.get('SESSION_SECRET'), httpOnly: false, cookie: { maxAge: TWO_WEEKS }})); + //oldApp.use(express.session()); // Initialize Passport! Also use passport.session() middleware, to support // persistent login sessions (recommended). - app.use(passport.initialize()); - app.use(passport.session()); + oldApp.use(passport.initialize()); + oldApp.use(passport.session()); - app.use(app.router); + oldApp.use(oldApp.router); var maxAge = isProd ? 31536000000 : 0; // Cache emojis without copying them to build, they are too many - app.use(express['static'](path.join(__dirname, "/../build"), { maxAge: maxAge })); - app.use('/common/dist', express['static'](publicDir + "/../../common/dist", { maxAge: maxAge })); - app.use('/common/audio', express['static'](publicDir + "/../../common/audio", { maxAge: maxAge })); - app.use('/common/script/public', express['static'](publicDir + "/../../common/script/public", { maxAge: maxAge })); - app.use('/common/img', express['static'](publicDir + "/../../common/img", { maxAge: maxAge })); - app.use(express['static'](publicDir)); + oldApp.use(express['static'](path.join(__dirname, "/../build"), { maxAge: maxAge })); + oldApp.use('/common/dist', express['static'](publicDir + "/../../common/dist", { maxAge: maxAge })); + oldApp.use('/common/audio', express['static'](publicDir + "/../../common/audio", { maxAge: maxAge })); + oldApp.use('/common/script/public', express['static'](publicDir + "/../../common/script/public", { maxAge: maxAge })); + oldApp.use('/common/img', express['static'](publicDir + "/../../common/img", { maxAge: maxAge })); + oldApp.use(express['static'](publicDir)); // Custom Directives - app.use(require('./routes/pages').middleware); - app.use(require('./routes/payments').middleware); - app.use(require('./routes/auth').middleware); - app.use(require('./routes/coupon').middleware); - app.use(require('./routes/unsubscription').middleware); + oldApp.use(require('./routes/pages').middleware); + oldApp.use(require('./routes/payments').middleware); + oldApp.use(require('./routes/api-v2/auth').middleware); + oldApp.use(require('./routes/api-v2/coupon').middleware); + oldApp.use(require('./routes/api-v2/unsubscription').middleware); var v2 = express(); - app.use('/api/v2', v2); - app.use('/api/v1', require('./routes/apiv1').middleware); - app.use('/export', require('./routes/dataexport').middleware); - require('./routes/apiv2.coffee')(swagger, v2); - app.use(require('./middlewares/errorHandler')); + oldApp.use('/api/v2', v2); + oldApp.use('/api/v1', require('./routes/api-v1').middleware); + oldApp.use('/export', require('./routes/dataexport').middleware); + require('./routes/api-v2/swagger')(swagger, v2); + oldApp.use(require('./middlewares/errorHandler')); server.on('request', app); server.listen(app.get("port"), function() {