v3: port static pages, make routes lib more flexible, share middlewares between v2 and v3, port v1, simplify server.js

This commit is contained in:
Matteo Pagliazzi
2016-03-30 17:20:01 +02:00
parent e54bd0f364
commit 6cbbdcdcbe
24 changed files with 420 additions and 267 deletions

View File

@@ -0,0 +1,12 @@
import {
requester,
} from '../../../../helpers/api-v3-integration.helper';
describe('GET /status', () => {
it('returns status: up', async () => {
let res = await requester().get('/status');
expect(res).to.eql({
status: 'up',
});
});
});

View File

@@ -0,0 +1,40 @@
/* eslint-disable global-require */
import {
generateRes,
generateReq,
generateNext,
} from '../../../../helpers/api-unit.helper';
import cors from '../../../../../website/src/middlewares/api-v3/cors';
describe('cors middleware', () => {
let res, req, next;
beforeEach(() => {
req = generateReq();
res = generateRes();
next = generateNext();
});
it('sets the correct headers', () => {
cors(req, res, next);
expect(res.set).to.have.been.calledWith({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE',
'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key',
});
expect(res.sendStatus).to.not.have.been.called;
expect(next).to.have.been.called.once;
});
it('responds immediately if method is OPTIONS', () => {
req.method = 'OPTIONS';
cors(req, res, next);
expect(res.set).to.have.been.calledWith({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE',
'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key',
});
expect(res.sendStatus).to.have.been.calledWith(200);
expect(next).to.not.have.been.called;
});
});

View File

@@ -119,6 +119,9 @@ describe('getUserLanguage', () => {
context('request with session', () => { context('request with session', () => {
it('uses the user preferred language if avalaible', (done) => { it('uses the user preferred language if avalaible', (done) => {
sandbox.stub(User, 'findOne').returns({ sandbox.stub(User, 'findOne').returns({
lean () {
return this;
},
exec () { exec () {
return Q.resolve({ return Q.resolve({
preferences: { preferences: {

View File

@@ -31,6 +31,7 @@ export function generateRes (options = {}) {
user: generateUser(options.localsUser), user: generateUser(options.localsUser),
group: generateGroup(options.localsGroup), group: generateGroup(options.localsGroup),
}, },
set: sandbox.stub(),
}; };
return defaults(options, defaultRes); return defaults(options, defaultRes);
@@ -41,6 +42,7 @@ export function generateReq (options = {}) {
body: {}, body: {},
query: {}, query: {},
headers: {}, headers: {},
header: sandbox.stub().returns(null),
}; };
return defaults(options, defaultReq); return defaults(options, defaultReq);

View File

@@ -0,0 +1,21 @@
let api = {};
/**
* @api {get} /status Get Habitica's status
* @apiVersion 3.0.0
* @apiName GetStatus
* @apiGroup Status
*
* @apiSuccess {status} string 'up' if everything is ok
*/
api.getStatus = {
method: 'GET',
url: '/status',
async handler (req, res) {
res.respond(200, {
status: 'up',
});
},
};
module.exports = api;

View File

@@ -0,0 +1,73 @@
import locals from '../middlewares/api-v3/locals';
import getUserLanguage from '../middlewares/api-v3/getUserLanguage';
import _ from 'lodash';
const marked = require('marked');
let api = {};
const TOTAL_USER_COUNT = '1,100,000';
api.getFrontPage = {
method: 'GET',
url: '/',
middlewares: [getUserLanguage, locals],
async handler (req, res) {
if (!req.header('x-api-user') && !req.header('x-api-key') && !(req.session && req.session.userId)) {
return res.redirect('/static/front');
}
res.render('index.jade', {
title: 'Habitica | Your Life The Role Playing Game',
env: res.locals.habitrpg,
});
},
};
let staticPages = ['front', 'privacy', 'terms', 'api', 'features',
'videos', 'contact', 'plans', 'new-stuff', 'community-guidelines',
'old-news', 'press-kit', 'faq', 'overview', 'apps',
'clear-browser-data', 'merch'];
_.each(staticPages, (name) => {
api[`get${name}Page`] = {
method: 'GET',
url: `/static/${name}`,
middlewares: [getUserLanguage, locals],
async handler (req, res) {
res.render(`static/${name}.jade`, {
env: res.locals.habitrpg,
marked: marked,
userCount: TOTAL_USER_COUNT
});
},
};
});
let shareables = ['level-up', 'hatch-pet', 'raise-pet', 'unlock-quest', 'won-challenge', 'achievement'];
_.each(shareables, (name) => {
api[`get${name}ShareablePage`] = {
method: 'GET',
url: `/social/${name}`,
middlewares: [getUserLanguage, locals],
async handler (req, res) {
res.render(`social/${name}`, {
env: res.locals.habitrpg,
marked: marked,
userCount: TOTAL_USER_COUNT
});
},
};
});
api.redirectExtensionsPage = {
method: 'GET',
url: '/static/extensions',
async handler (req, res) {
res.redirect('http://habitica.wikia.com/wiki/App_and_Extension_Integrations');
},
};
module.exports = api;

View File

@@ -167,9 +167,6 @@ module.exports.analytics = { track: function() { }, trackPurchase: function() {
* Load nconf and define default configuration values if config.json or ENV vars are not found * Load nconf and define default configuration values if config.json or ENV vars are not found
*/ */
module.exports.setupConfig = function(){ module.exports.setupConfig = function(){
IS_PROD = nconf.get('NODE_ENV') === 'production';
BASE_URL = nconf.get('BASE_URL');
if (nconf.get('IS_DEV')) if (nconf.get('IS_DEV'))
Error.stackTraceLimit = Infinity; Error.stackTraceLimit = Infinity;
if (IS_PROD && nconf.get('NEW_RELIC_ENABLED') === 'true') if (IS_PROD && nconf.get('NEW_RELIC_ENABLED') === 'true')

View File

@@ -0,0 +1,31 @@
import fs from 'fs';
import _ from 'lodash';
// Wrapper function to handler `async` route handlers that return promises
// It takes the async function, execute it and pass any error to next (args[2])
let _wrapAsyncFn = fn => (...args) => fn(...args).catch(args[2]);
let noop = (req, res, next) => next();
module.exports.readController = function readController (router, controller) {
_.each(controller, (action) => {
let {method, url, middlewares = [], handler} = action;
method = method.toLowerCase();
let fn = handler ? _wrapAsyncFn(handler) : noop;
router[method](url, ...middlewares, fn);
});
};
module.exports.walkControllers = function walkControllers (router, filePath) {
fs
.readdirSync(filePath)
.forEach(fileName => {
if (!fs.statSync(filePath + fileName).isFile()) {
walkControllers(router, `${filePath}${fileName}/`);
} else if (fileName.match(/\.js$/)) {
let controller = require(filePath + fileName); // eslint-disable-line global-require
module.exports.readController(router, controller);
}
});
};

View File

@@ -0,0 +1,21 @@
import nconf from 'nconf';
import logger from './logger';
import autoinc from 'mongoose-id-autoinc';
import mongoose from 'mongoose';
import Q from 'q';
const IS_PROD = nconf.get('IS_PROD');
// Use Q promises instead of mpromise in mongoose
mongoose.Promise = Q.Promise;
let mongooseOptions = !IS_PROD ? {} : {
replset: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
};
let db = mongoose.connect(nconf.get('NODE_DB_URI'), mongooseOptions, (err) => {
if (err) throw err;
logger.info('Connected with Mongoose.');
});
autoinc.init(db);

View File

@@ -0,0 +1,24 @@
import passport from 'passport';
import nconf from 'nconf';
import passportFacebook from 'passport-facebook';
const FacebookStrategy = passportFacebook.Strategy;
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing. However, since this example does not
// have a database of user records, the complete Facebook profile is serialized
// and deserialized.
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((obj, done) => done(null, obj));
// TODO
// This auth strategy is no longer used. It's just kept around for auth.js#loginFacebook() (passport._strategies.facebook.userProfile)
// The proper fix would be to move to a general OAuth module simply to verify accessTokens
passport.use(new FacebookStrategy({
clientID: nconf.get('FACEBOOK_KEY'),
clientSecret: nconf.get('FACEBOOK_SECRET'),
// callbackURL: nconf.get("BASE_URL") + "/auth/facebook/callback"
}, (accessToken, refreshToken, profile, done) => done(null, profile)));

View File

@@ -1,37 +0,0 @@
import fs from 'fs';
import path from 'path';
import express from 'express';
import _ from 'lodash';
const CONTROLLERS_PATH = path.join(__dirname, '/../../controllers/api-v3/');
let router = express.Router(); // eslint-disable-line babel/new-cap
// Wrapper function to handler `async` route handlers that return promises
// It takes the async function, execute it and pass any error to next (args[2])
let _wrapAsyncFn = fn => (...args) => fn(...args).catch(args[2]);
let noop = (req, res, next) => next();
function walkControllers (filePath) {
fs
.readdirSync(filePath)
.forEach(fileName => {
if (!fs.statSync(filePath + fileName).isFile()) {
walkControllers(`${filePath}${fileName}/`);
} else if (fileName.match(/\.js$/)) {
let controller = require(filePath + fileName); // eslint-disable-line global-require
_.each(controller, (action) => {
let {method, url, middlewares = [], handler} = action;
method = method.toLowerCase();
let fn = handler ? _wrapAsyncFn(handler) : noop;
router[method](url, ...middlewares, fn);
});
}
});
}
walkControllers(CONTROLLERS_PATH);
module.exports = router;

View File

@@ -0,0 +1,9 @@
module.exports = function corsMiddleware (req, res, next) {
res.set({
'Access-Control-Allow-Origin': req.header('origin') || '*',
'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE',
'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key',
});
if (req.method === 'OPTIONS') return res.sendStatus(200);
return next();
};

View File

@@ -75,6 +75,7 @@ module.exports = function getUserLanguage (req, res, next) {
User.findOne({ User.findOne({
_id: req.session.userId, _id: req.session.userId,
}, 'preferences.language') }, 'preferences.language')
.lean()
.exec() .exec()
.then((user) => { .then((user) => {
req.language = _getFromUser(user, req); req.language = _getFromUser(user, req);

View File

@@ -1,48 +1,80 @@
// This module is only used to attach middlewares to the express app // This module is only used to attach middlewares to the express app
import expressValidator from 'express-validator';
import getUserLanguage from './getUserLanguage';
import analytics from './analytics';
import errorHandler from './errorHandler'; import errorHandler from './errorHandler';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import routes from '../../libs/api-v3/setupRoutes';
import notFoundHandler from './notFound'; import notFoundHandler from './notFound';
import nconf from 'nconf'; import nconf from 'nconf';
import morgan from 'morgan'; import morgan from 'morgan';
import responseHandler from './response';
import setupBody from './setupBody';
import cookieSession from 'cookie-session'; import cookieSession from 'cookie-session';
import cors from './cors';
import staticMiddleware from './static';
import domainMiddleware from './domain';
import mongoose from 'mongoose';
import compression from 'compression';
import favicon from 'serve-favicon';
import methodOverride from 'method-override';
import passport from 'passport';
import path from 'path';
import express from 'express';
import routes from '../../libs/api-v3/routes';
import {
forceSSL,
forceHabitica,
} from './redirects';
import v1 from './v1';
import v2 from './v2';
import v3 from './v3';
import staticPagesController from '../../controllers/pages';
const IS_PROD = nconf.get('IS_PROD'); const IS_PROD = nconf.get('IS_PROD');
const DISABLE_LOGGING = nconf.get('DISABLE_REQUEST_LOGGING'); const DISABLE_LOGGING = nconf.get('DISABLE_REQUEST_LOGGING');
const PUBLIC_DIR = path.join(__dirname, '/../../../public');
const SESSION_SECRET = nconf.get('SESSION_SECRET'); const SESSION_SECRET = nconf.get('SESSION_SECRET');
const TWO_WEEKS = 1000 * 60 * 60 * 24 * 14; const TWO_WEEKS = 1000 * 60 * 60 * 24 * 14;
module.exports = function attachMiddlewares (app) { module.exports = function attachMiddlewares (app, server) {
app.use(domainMiddleware(server, mongoose));
if (!IS_PROD && !DISABLE_LOGGING) app.use(morgan('dev')); if (!IS_PROD && !DISABLE_LOGGING) app.use(morgan('dev'));
// TODO handle errors app.use(compression());
app.use(favicon(`${PUBLIC_DIR}/favicon.ico`));
app.use(cors);
app.use(forceSSL);
app.use(forceHabitica);
// TODO if we don't manage to move the client off $resource the limit for bodyParser.json must be increased to 1mb from 100kb (default)
app.use(bodyParser.urlencoded({ app.use(bodyParser.urlencoded({
extended: true, // Uses 'qs' library as old connect middleware extended: true, // Uses 'qs' library as old connect middleware
})); }));
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(methodOverride()); // TODO still needed in 2016?
app.use(cookieSession({ app.use(cookieSession({
name: 'connect:sess', // Used to keep backward compatibility with Express 3 cookies name: 'connect:sess', // Used to keep backward compatibility with Express 3 cookies
secret: SESSION_SECRET, secret: SESSION_SECRET,
httpOnly: false, // TODO this should be true for security, what about https only? httpOnly: false, // TODO this should be true for security, what about https only?
maxAge: TWO_WEEKS, maxAge: TWO_WEEKS,
})); }));
app.use(expressValidator());
app.use(analytics);
app.use(setupBody);
app.use(responseHandler);
app.use(getUserLanguage);
app.set('view engine', 'jade');
app.set('views', `${__dirname}/../../../views`);
app.use('/api/v3', routes); // Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
const staticPagesRouter = express.Router(); // eslint-disable-line babel/new-cap
routes.readController(staticPagesRouter, staticPagesController);
app.use('/', staticPagesRouter);
app.use('/api/v3', v3);
app.use('/api/v2', v2);
app.use('/api/v1', v1);
staticMiddleware(app);
app.use(notFoundHandler); app.use(notFoundHandler);
// Error handler middleware, define as the last one // Error handler middleware, define as the last one.
// Used for v3 and v1, v2 will keep using its own error handler
app.use(errorHandler); app.use(errorHandler);
}; };

View File

@@ -0,0 +1,43 @@
import nconf from 'nconf';
const IS_PROD = nconf.get('IS_PROD');
const IGNORE_REDIRECT = nconf.get('IGNORE_REDIRECT');
const BASE_URL = nconf.get('BASE_URL');
function isHTTP (req) {
return ( // eslint-disable-line no-extra-parens
req.header('x-forwarded-proto') &&
req.header('x-forwarded-proto') !== 'https' &&
IS_PROD &&
BASE_URL.indexOf('https') === 0
);
}
function isProxied (req) {
return ( // eslint-disable-line no-extra-parens
req.header('x-habitica-lb') &&
req.header('x-habitica-lb') === 'Yes'
);
}
export function forceSSL (req, res, next) {
if (isHTTP(req) && !isProxied(req)) {
return res.redirect(BASE_URL + req.originalUrl);
}
next();
}
// Redirect to habitica for non-api urls
function nonApiUrl (req) {
return req.originalUrl.search(/\/api\//) === -1;
}
export function forceHabitica (req, res, next) {
if (IS_PROD && !IGNORE_REDIRECT && !isProxied(req) && nonApiUrl(req)) {
return res.redirect(301, BASE_URL + req.url);
}
next();
}

View File

@@ -0,0 +1,19 @@
// API v1 middlewares and routes
// DEPRECATED AND INACTIVE
import express from 'express';
import nconf from 'nconf';
import {
NotFound,
} from '../../libs/api-v3/errors';
const router = express.Router(); // eslint-disable-line babel/new-cap
const BASE_URL = nconf.get('BASE_URL');
router.all('*', function deprecatedV1 (req, res, next) {
let error = new NotFound(`API v1 is no longer supported, please use API v3 instead (${BASE_URL}/static/api).`);
return next(error);
});
module.exports = router;

View File

@@ -0,0 +1,26 @@
// DEPRECATED BUT STILL ACTIVE
// import path from 'path';
// import swagger from 'swagger-node-express';
// import shared from '../../../../common';
import express from 'express';
const v2app = express();
// re-set the view options because they are not inherited from the top level app
v2app.set('view engine', 'jade');
v2app.set('views', `${__dirname}/../../../views`);
// Custom Directives
// v2app.use('/', require('../../routes/api-v2/auth'));
// v2app.use('/', require('../../routes/api-v2/coupon'));
// v2app.use('/', require('../../routes/api-v2/unsubscription'));
// const v2routes = express();
// v2app.use('/api/v2', v2routes);
// v2app.use('/export', require('../../routes/dataexport'));
// require('../../routes/api-v2/swagger')(swagger, v2);
// v2app.use(require('../api-v2/errorHandler'));
module.exports = v2app;

View File

@@ -0,0 +1,27 @@
import express from 'express';
import expressValidator from 'express-validator';
import getUserLanguage from './getUserLanguage';
import responseHandler from './response';
import analytics from './analytics';
import setupBody from './setupBody';
import routes from '../../libs/api-v3/routes';
import path from 'path';
const v3app = express();
// re-set the view options because they are not inherited from the top level app
v3app.set('view engine', 'jade');
v3app.set('views', `${__dirname}/../../../views`);
v3app.use(expressValidator());
v3app.use(analytics);
v3app.use(setupBody);
v3app.use(responseHandler);
v3app.use(getUserLanguage); // TODO move to after auth for authenticated routes
const CONTROLLERS_PATH = path.join(__dirname, '/../../controllers/api-v3/');
const router = express.Router(); // eslint-disable-line babel/new-cap
routes.walkControllers(router, CONTROLLERS_PATH);
v3app.use(router);
module.exports = v3app;

View File

@@ -3,6 +3,9 @@ var limiter = require('connect-ratelimit');
var IS_PROD = nconf.get('NODE_ENV') === 'production'; var IS_PROD = nconf.get('NODE_ENV') === 'production';
// TODO since Habitica runs on many different servers this module is pretty useless
// as it will only block requests that go to the same server
module.exports = function(app) { module.exports = function(app) {
// TODO review later // TODO review later
// disable the rate limiter middleware // disable the rate limiter middleware

View File

@@ -1,7 +0,0 @@
module.exports = function(req, res, next) {
res.header("Access-Control-Allow-Origin", req.headers.origin || "*");
res.header("Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,HEAD,DELETE");
res.header("Access-Control-Allow-Headers", "Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key");
if (req.method === 'OPTIONS') return res.sendStatus(200);
return next();
};

View File

@@ -1,3 +1,5 @@
// TODO do we need this module?
module.exports.siteVersion = 1; module.exports.siteVersion = 1;
module.exports.middleware = function(req, res, next){ module.exports.middleware = function(req, res, next){

View File

@@ -1,41 +0,0 @@
var nconf = require('nconf');
var IS_PROD = nconf.get('NODE_ENV') === 'production';
var ignoreRedirect = nconf.get('IGNORE_REDIRECT');
var BASE_URL = nconf.get('BASE_URL');
function isHTTP(req) {
return (
req.headers['x-forwarded-proto'] &&
req.headers['x-forwarded-proto'] !== 'https' &&
IS_PROD &&
BASE_URL.indexOf('https') === 0
);
}
function isProxied(req) {
return (
req.headers['x-habitica-lb'] &&
req.headers['x-habitica-lb'] === 'Yes'
);
}
module.exports.forceSSL = function(req, res, next){
if(isHTTP(req) && !isProxied(req)) {
return res.redirect(BASE_URL + req.url);
}
next();
};
// Redirect to habitica for non-api urls
function nonApiUrl(req) {
return req.url.search(/\/api\//) === -1;
}
module.exports.forceHabitica = function(req, res, next) {
if (IS_PROD && !ignoreRedirect && !isProxied(req) && nonApiUrl(req)) {
return res.redirect(301, BASE_URL + req.url);
}
next();
};

View File

@@ -1,13 +0,0 @@
var express = require('express');
var router = express.Router();
var nconf = require('nconf');
/* ---------- Deprecated API ------------*/
router.all('*', function deprecated(req, res, next) {
res.json(404, {
err: 'API v1 is no longer supported, please use API v2 instead ' + nconf.get('BASE_URL') + '/static/api'
});
});
module.exports = router;

View File

@@ -1,170 +1,35 @@
// TODO cleanup all comments when API v3 is finished
import nconf from 'nconf'; import nconf from 'nconf';
import logger from './libs/api-v3/logger'; import logger from './libs/api-v3/logger';
import express from 'express'; import express from 'express';
import http from 'http'; import http from 'http';
// import path from 'path';
// let swagger = require('swagger-node-express');
import autoinc from 'mongoose-id-autoinc';
import passport from 'passport';
// let shared = require('../../common');
import passportFacebook from 'passport-facebook';
import mongoose from 'mongoose';
import Q from 'q';
import domainMiddleware from './middlewares/api-v3/domain';
import attachMiddlewares from './middlewares/api-v3/index'; import attachMiddlewares from './middlewares/api-v3/index';
import staticMiddleware from './middlewares/api-v3/static';
const server = http.createServer();
const app = express();
app.set('port', nconf.get('PORT'));
// Setup translations // Setup translations
// let i18n = require('./libs/api-v2/i18n'); import './libs/api-v3/i18n';
const IS_PROD = nconf.get('IS_PROD');
// const IS_DEV = nconf.get('IS_DEV');
// const DISABLE_LOGGING = nconf.get('DISABLE_REQUEST_LOGGING');
// const TWO_WEEKS = 1000 * 60 * 60 * 24 * 14;
let server = http.createServer();
let app = express();
// Mongoose configuration
// Use Q promises instead of mpromise in mongoose
mongoose.Promise = Q.Promise;
let mongooseOptions = !IS_PROD ? {} : {
replset: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
};
let db = mongoose.connect(nconf.get('NODE_DB_URI'), mongooseOptions, (err) => {
if (err) throw err;
logger.info('Connected with Mongoose');
});
autoinc.init(db);
// Load config files
import './libs/api-v3/setupMongoose';
import './libs/api-v3/firebase'; import './libs/api-v3/firebase';
import './libs/api-v3/setupPassport';
// load schemas & models // Load some schemas & models
import './models/challenge'; import './models/challenge';
import './models/group'; import './models/group';
import './models/user'; import './models/user';
// ------------ Passport Configuration ------------ app.set('view engine', 'jade');
// let util = require('util') app.set('views', `${__dirname}/../views`);
let FacebookStrategy = passportFacebook.Strategy;
// Passport session setup. attachMiddlewares(app, server);
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing. However, since this example does not
// have a database of user records, the complete Facebook profile is serialized
// and deserialized.
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((obj, done) => done(null, obj));
// FIXME
// This auth strategy is no longer used. It's just kept around for auth.js#loginFacebook() (passport._strategies.facebook.userProfile)
// The proper fix would be to move to a general OAuth module simply to verify accessTokens
passport.use(new FacebookStrategy({
clientID: nconf.get('FACEBOOK_KEY'),
clientSecret: nconf.get('FACEBOOK_SECRET'),
// callbackURL: nconf.get("BASE_URL") + "/auth/facebook/callback"
}, (accessToken, refreshToken, profile, done) => done(null, profile)));
// ------------ Server Configuration ------------
// let publicDir = path.join(__dirname, '/../public');
app.set('port', nconf.get('PORT'));
// 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
let oldApp = express(); // api v1 and v2, and not scoped routes
let newApp = express(); // api v3
app.use(domainMiddleware(server, mongoose));
// Route requests to the right app
// 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/*', newApp);
// TODO change ^ so that all routes except those marked explictly with api/v2 goes to oldApp
// Mount middlewares for the new app (api v3)
attachMiddlewares(newApp);
/* OLD APP IS DISABLED UNTIL COMPATIBLE WITH NEW MODELS
//require('./middlewares/apiThrottle')(oldApp);
oldApp.use(require('./middlewares/api-v2/domain')(server,mongoose));
if (!IS_PROD && !DISABLE_LOGGING) oldApp.use(require('morgan')("dev"));
oldApp.use(require('compression')());
oldApp.set("views", __dirname + "/../views");
oldApp.set("view engine", "jade");
oldApp.use(require('serve-favicon')(publicDir + '/favicon.ico'));
oldApp.use(require('./middlewares/cors'));
var redirects = require('./middlewares/redirects');
oldApp.use(redirects.forceHabitica);
oldApp.use(redirects.forceSSL);
var bodyParser = require('body-parser');
// Default limit is 100kb, need that because we actually send whole groups to the server
// FIXME as soon as possible (need to move on the client from $resource -> $http)
var BODY_PARSER_LIMIT = '1mb';
oldApp.use(bodyParser.urlencoded({
extended: true,
parameterLimit: 10000, // Upped for safety from 1k, FIXME as above
limit: BODY_PARSER_LIMIT,
}));
oldApp.use(bodyParser.json({
limit: BODY_PARSER_LIMIT,
}));
oldApp.use(require('method-override')());
oldApp.use(require('cookie-session')({
name: 'connect:sess', // Used to keep backward compatibility with Express 3 cookies
secret: nconf.get('SESSION_SECRET'),
httpOnly: false,
maxAge: TWO_WEEKS
}));
// Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
oldApp.use(passport.initialize());
oldApp.use(passport.session());
// Custom Directives
oldApp.use('/', require('./routes/pages'));
oldApp.use('/', require('./routes/payments'));
oldApp.use('/', require('./routes/api-v2/auth'));
oldApp.use('/', require('./routes/api-v2/coupon'));
oldApp.use('/', require('./routes/api-v2/unsubscription'));
var v2 = express();
oldApp.use('/api/v2', v2);
oldApp.use('/api/', require('./routes/api-v1'));
oldApp.use('/export', require('./routes/dataexport'));
require('./routes/api-v2/swagger')(swagger, v2);
// Cache emojis without copying them to build, they are too many
oldApp.use(require('./middlewares/api-v2/errorHandler'));
*
let maxAge = IS_PROD ? 31536000000 : 0;
oldApp.use(express.static(path.join(__dirname, '/../build'), { maxAge }));
oldApp.use('/common/dist', express.static(`${publicDir}/../../common/dist`, { maxAge }));
oldApp.use('/common/audio', express.static(`${publicDir}/../../common/audio`, { maxAge }));
oldApp.use('/common/script/public', express.static(`${publicDir}/../../common/script/public`, { maxAge }));
oldApp.use('/common/img', express.static(`${publicDir}/../../common/img`, { maxAge }));
oldApp.use(express.static(publicDir));
*/
staticMiddleware(app);
server.on('request', app); server.on('request', app);
server.listen(app.get('port'), () => { server.listen(app.get('port'), () => {
return logger.info(`Express server listening on port ${app.get('port')}`); logger.info(`Express server listening on port ${app.get('port')}`);
}); });
module.exports = server; module.exports = server;