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() {