diff --git a/.eslintrc b/.eslintrc
index 69c945431e..a3bc6debda 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -28,7 +28,6 @@
"no-new": 2,
"no-octal-escape": 2,
"no-octal": 2,
- "no-param-reassign": 2,
"no-process-env": 2,
"no-proto": 2,
"no-implied-eval": 2,
diff --git a/Gruntfile.js b/Gruntfile.js
index 7d8ea5a997..40752c9d90 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -130,7 +130,7 @@ module.exports = function(grunt) {
grunt.registerTask('test:prepare:translations', function() {
require('babel/register');
- var i18n = require('./website/src/libs/i18n'),
+ var i18n = require('./website/src/libs/api-v3/i18n'),
fs = require('fs');
fs.writeFileSync('test/spec/mocks/translations.js',
"if(!window.env) window.env = {};\n" +
diff --git a/package.json b/package.json
index 81c577c43b..f13295e9e6 100644
--- a/package.json
+++ b/package.json
@@ -12,9 +12,9 @@
"babel-core": "^5.8.34",
"babelify": "^6.x.x",
"body-parser": "^1.14.1",
- "compression": "^1.6.0",
"bower": "~1.3.12",
"browserify": "~12.0.1",
+ "compression": "^1.6.0",
"connect-ratelimit": "0.0.7",
"cookie-parser": "^1.4.0",
"cookie-session": "^1.2.0",
@@ -63,7 +63,7 @@
"nconf": "~0.8.2",
"newrelic": "~1.23.0",
"nib": "~1.0.1",
- "nodemailer": "~0.5.2",
+ "nodemailer": "^1.9.0",
"pageres": "^1.0.1",
"passport": "~0.2.1",
"passport-facebook": "2.0.0",
diff --git a/tasks/gulp-console.js b/tasks/gulp-console.js
index 46f4ea44ea..96af26a27a 100644
--- a/tasks/gulp-console.js
+++ b/tasks/gulp-console.js
@@ -2,7 +2,6 @@ import mongoose from 'mongoose';
import autoinc from 'mongoose-id-autoinc';
import logger from '../website/src/libs/api-v3/logger';
import nconf from 'nconf';
-import utils from '../website/src/libs/utils';
import repl from 'repl';
import gulp from 'gulp';
@@ -19,8 +18,6 @@ let improveRepl = (context) => {
process.stdout.write('\u001B[2J\u001B[0;0f');
}});
- utils.setupConfig();
-
context.Challenge = require('../website/src/models/challenge').model;
context.Group = require('../website/src/models/group').model;
context.User = require('../website/src/models/user').model;
diff --git a/test/api/v3/unit/libs/buildManifest.test.js b/test/api/v3/unit/libs/buildManifest.test.js
new file mode 100644
index 0000000000..d03e19c9eb
--- /dev/null
+++ b/test/api/v3/unit/libs/buildManifest.test.js
@@ -0,0 +1,18 @@
+import {
+ getManifestFiles,
+} from '../../../../../website/src/libs/api-v3/buildManifest';
+
+describe('Build Manifest', () => {
+ describe('getManifestFiles', () => {
+ it('returns an html string', () => {
+ let htmlCode = getManifestFiles('app');
+
+ expect(htmlCode.startsWith('`; // eslint-disable-line prefer-template
+ } else {
+ files.css.forEach((file) => {
+ htmlCode += ``;
+ });
+ files.js.forEach((file) => {
+ htmlCode += ``;
+ });
+ }
+
+ return htmlCode;
+}
\ No newline at end of file
diff --git a/website/src/libs/api-v3/email.js b/website/src/libs/api-v3/email.js
new file mode 100644
index 0000000000..65cbcce02a
--- /dev/null
+++ b/website/src/libs/api-v3/email.js
@@ -0,0 +1,158 @@
+import { createTransport } from 'nodemailer';
+import nconf from 'nconf';
+import logger from './logger';
+import { encrypt } from './encryption';
+import request from 'request';
+
+const IS_PROD = nconf.get('IS_PROD');
+const EMAIL_SERVER = {
+ url: nconf.get('EMAIL_SERVER:url'),
+ auth: {
+ user: nconf.get('EMAIL_SERVER:authUser'),
+ password: nconf.get('EMAIL_SERVER:authPassword'),
+ },
+};
+const BASE_URL = nconf.get('BASE_URL');
+
+let smtpTransporter = createTransport({
+ service: nconf.get('SMTP_SERVICE'),
+ auth: {
+ user: nconf.get('SMTP_USER'),
+ pass: nconf.get('SMTP_PASS'),
+ },
+});
+
+// Send email directly from the server using the smtpTransporter,
+// used only to send password reset emails because users unsubscribed on Mandrill wouldn't get them
+export function send (mailData) {
+ return smtpTransporter
+ .sendMail(mailData)
+ .catch((error) => logger.error(error));
+}
+
+export function getUserInfo (user, fields = []) {
+ let info = {};
+
+ if (fields.indexOf('name') !== -1) {
+ info.name = user.profile && user.profile.name;
+
+ if (!info.name) {
+ if (user.auth.local && user.auth.local.username) {
+ info.name = user.auth.local.username;
+ } else if (user.auth.facebook) {
+ info.name = user.auth.facebook.displayName || user.auth.facebook.username;
+ }
+ }
+ }
+
+ if (fields.indexOf('email') !== -1) {
+ if (user.auth.local && user.auth.local.email) {
+ info.email = user.auth.local.email;
+ } else if (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0] && user.auth.facebook.emails[0].value) {
+ info.email = user.auth.facebook.emails[0].value;
+ }
+ }
+
+ if (fields.indexOf('_id') !== -1) {
+ info._id = user._id;
+ }
+
+ if (fields.indexOf('canSend') !== -1) {
+ if (user.preferences && user.preferences.emailNotifications) {
+ info.canSend = user.preferences.emailNotifications.unsubscribeFromAll !== true;
+ }
+ }
+
+ return info;
+}
+
+// Send a transactional email using Mandrill through the external email server
+export function sendTxn (mailingInfoArray, emailType, variables, personalVariables) {
+ mailingInfoArray = Array.isArray(mailingInfoArray) ? mailingInfoArray : [mailingInfoArray];
+
+ variables = [
+ {name: 'BASE_URL', content: BASE_URL},
+ ].concat(variables || []);
+
+ // It's important to pass at least a user with its `preferences` as we need to check if he unsubscribed
+ mailingInfoArray = mailingInfoArray.map((mailingInfo) => {
+ return mailingInfo._id ? getUserInfo(mailingInfo, ['_id', 'email', 'name', 'canSend']) : mailingInfo;
+ }).filter((mailingInfo) => {
+ // Always send reset-password emails
+ // Don't check canSend for non registered users as already checked before
+ return mailingInfo.email && (!mailingInfo._id || mailingInfo.canSend || emailType === 'reset-password');
+ });
+
+ // Personal variables are personal to each email recipient, if they are missing
+ // we manually create a structure for them with RECIPIENT_NAME and RECIPIENT_UNSUB_URL
+ // otherwise we just add RECIPIENT_NAME and RECIPIENT_UNSUB_URL to the existing personal variables
+ if (!personalVariables || personalVariables.length === 0) {
+ personalVariables = mailingInfoArray.map((mailingInfo) => {
+ return {
+ rcpt: mailingInfo.email,
+ vars: [
+ {
+ name: 'RECIPIENT_NAME',
+ content: mailingInfo.name,
+ },
+ {
+ name: 'RECIPIENT_UNSUB_URL',
+ content: `/unsubscribe?code=${encrypt(JSON.stringify({
+ _id: mailingInfo._id,
+ email: mailingInfo.email,
+ }))}`,
+ },
+ ],
+ };
+ });
+ } else {
+ let temporaryPersonalVariables = {};
+
+ mailingInfoArray.forEach((mailingInfo) => {
+ temporaryPersonalVariables[mailingInfo.email] = {
+ name: mailingInfo.name,
+ _id: mailingInfo._id,
+ };
+ });
+
+ personalVariables.forEach((singlePersonalVariables) => {
+ singlePersonalVariables.vars.push(
+ {
+ name: 'RECIPIENT_NAME',
+ content: temporaryPersonalVariables[singlePersonalVariables.rcpt].name,
+ },
+ {
+ name: 'RECIPIENT_UNSUB_URL',
+ content: `/unsubscribe?code=${encrypt(JSON.stringify({
+ _id: temporaryPersonalVariables[singlePersonalVariables.rcpt]._id,
+ email: singlePersonalVariables.rcpt,
+ }))}`,
+ }
+ );
+ });
+ }
+
+ if (IS_PROD && mailingInfoArray.length > 0) {
+ request.post({
+ url: `${EMAIL_SERVER.url}/job`,
+ auth: {
+ user: EMAIL_SERVER.auth.user,
+ pass: EMAIL_SERVER.auth.password,
+ },
+ json: {
+ type: 'email',
+ data: {
+ emailType,
+ to: mailingInfoArray,
+ variables,
+ personalVariables,
+ },
+ options: {
+ priority: 'high',
+ attempts: 5,
+ backoff: {delay: 10 * 60 * 1000, type: 'fixed'},
+ },
+ },
+ }, (err) => logger.error(err));
+ }
+}
diff --git a/website/src/libs/api-v3/encryption.js b/website/src/libs/api-v3/encryption.js
new file mode 100644
index 0000000000..0f5f9d83dd
--- /dev/null
+++ b/website/src/libs/api-v3/encryption.js
@@ -0,0 +1,25 @@
+import {
+ createCipher,
+ createDecipher,
+} from 'crypto';
+import nconf from 'nconf';
+
+// TODO check this is secure
+const algorithm = 'aes-256-ctr';
+const SESSION_SECRET = nconf.get('SESSION_SECRET');
+
+export function encrypt (text) {
+ let cipher = createCipher(algorithm, SESSION_SECRET);
+ let crypted = cipher.update(text, 'utf8', 'hex');
+
+ crypted += cipher.final('hex');
+ return crypted;
+}
+
+export function decrypt (text) {
+ let decipher = createDecipher(algorithm, SESSION_SECRET);
+ let dec = decipher.update(text, 'hex', 'utf8');
+
+ dec += decipher.final('utf8');
+ return dec;
+}
\ No newline at end of file
diff --git a/website/src/libs/api-v3/firebase.js b/website/src/libs/api-v3/firebase.js
new file mode 100644
index 0000000000..92d6c28fc1
--- /dev/null
+++ b/website/src/libs/api-v3/firebase.js
@@ -0,0 +1,67 @@
+import Firebase from 'firebase';
+import nconf from 'nconf';
+const FIREBASE_CONFIG = nconf.get('FIREBASE');
+const FIREBASE_ENABLED = FIREBASE_CONFIG.ENABLED === 'true';
+
+let firebaseRef;
+
+if (FIREBASE_ENABLED) {
+ firebaseRef = new Firebase(`https://${FIREBASE_CONFIG.APP}.firebaseio.com`);
+
+ // TODO what happens if an op is sent before client is authenticated?
+ firebaseRef.authWithCustomToken(FIREBASE_CONFIG.SECRET, (err) => {
+ // TODO it's ok to kill the server here? what if FB is offline?
+ if (err) throw new Error('Impossible to authenticate Firebase');
+ });
+}
+
+export function updateGroupData (group) {
+ if (!FIREBASE_ENABLED) return;
+ // TODO is throw ok? we don't have callbacks
+ if (!group) throw new Error('group obj is required.');
+ // Return in case of tavern (comparison working because we use string for _id)
+ if (group._id === 'habitrpg') return;
+
+ firebaseRef.child(`rooms/${group._id}`)
+ .set({
+ name: group.name,
+ });
+}
+
+export function addUserToGroup (groupId, userId) {
+ if (!FIREBASE_ENABLED) return;
+ if (!userId || !groupId) throw new Error('groupId, userId are required.');
+ if (groupId === 'habitrpg') return;
+
+ firebaseRef.child(`members/${groupId}/${userId}`).set(true);
+ firebaseRef.child(`users/${userId}/rooms/${groupId}`).set(true);
+}
+
+export function removeUserFromGroup (groupId, userId) {
+ if (!FIREBASE_ENABLED) return;
+ if (!userId || !groupId) throw new Error('groupId, userId are required.');
+ if (groupId === 'habitrpg') return;
+
+ firebaseRef.child(`members/${groupId}/${userId}`).remove();
+ firebaseRef.child(`users/${userId}/rooms/${groupId}`).remove();
+}
+
+export function deleteGroup (groupId) {
+ if (!FIREBASE_ENABLED) return;
+ if (!groupId) throw new Error('groupId is required.');
+ if (groupId === 'habitrpg') return;
+
+ firebaseRef.child(`members/${groupId}`).remove();
+ // FIXME not really necessary as long as we only store room data,
+ // as empty objects are automatically deleted (/members/... in future...)
+ firebaseRef.child(`rooms/${groupId}`).remove();
+}
+
+// FIXME not really necessary as long as we only store room data,
+// as empty objects are automatically deleted
+export function deleteUser (userId) {
+ if (!FIREBASE_ENABLED) return;
+ if (!userId) throw new Error('userId is required.');
+
+ firebaseRef.child(`users/${userId}`).remove();
+}
\ No newline at end of file
diff --git a/website/src/libs/api-v3/i18n.js b/website/src/libs/api-v3/i18n.js
new file mode 100644
index 0000000000..ed2134f7bf
--- /dev/null
+++ b/website/src/libs/api-v3/i18n.js
@@ -0,0 +1,118 @@
+import fs from 'fs';
+import path from 'path';
+import _ from 'lodash';
+import shared from '../../../../common';
+
+export const localePath = path.join(__dirname, '/../../../../common/locales/');
+
+// Store translations
+export let translations = {};
+// Store MomentJS localization files
+export let momentLangs = {};
+
+// Handle differencies in language codes between MomentJS and /locales
+let momentLangsMapping = {
+ en: 'en-gb',
+ en_GB: 'en-gb', // eslint-disable-line camelcase
+ no: 'nn',
+ zh: 'zh-cn',
+ es_419: 'es', // eslint-disable-line camelcase
+};
+
+function _loadTranslations (locale) {
+ let files = fs.readdirSync(path.join(localePath, locale));
+
+ translations[locale] = {};
+
+ files.forEach((file) => {
+ if (path.extname(file) !== '.json') return;
+
+ // We use require to load and parse a JSON file
+ _.merge(translations[locale], require(path.join(localePath, locale, file))); // eslint-disable-line global-require
+ });
+}
+
+// First fetch English strings so we can merge them with missing strings in other languages
+_loadTranslations('en');
+
+// Then load all other languages
+fs.readdirSync(localePath).forEach((file) => {
+ if (file === 'en' || fs.statSync(path.join(localePath, file)).isDirectory() === false) return;
+ _loadTranslations(file);
+
+ // Merge missing strings from english
+ _.defaults(translations[file], translations.en);
+});
+
+// Add translations to shared
+shared.i18n.translations = translations;
+
+export let langCodes = Object.keys(translations);
+
+export let avalaibleLanguages = langCodes.map((langCode) => {
+ return {
+ code: langCode,
+ name: translations[langCode].languageName,
+ };
+});
+
+langCodes.forEach((code) => {
+ let lang = _.find(avalaibleLanguages, {code});
+
+ lang.momentLangCode = momentLangsMapping[code] || code;
+
+ try {
+ // MomentJS lang files are JS files that has to be executed in the browser so we load them as plain text files
+ // We wrap everything in a try catch because the file might not exist
+ let f = fs.readFileSync(path.join(__dirname, `/../../../node_modules/moment/locale/${lang.momentLangCode}.js`), 'utf8');
+
+ momentLangs[code] = f;
+ } catch (e) { // eslint-disable-lint no-empty
+ // TODO implement some type of error loggin?
+ // The catch block is mandatory so can't be removed
+ }
+});
+
+// Remove en_GB from langCodes checked by browser to avoid it being
+// used in place of plain original 'en' (it's an optional language that can be enabled only in setting)
+export let defaultLangCodes = _.without(langCodes, 'en_GB');
+
+// A map of languages that have different versions and the relative versions
+export let multipleVersionsLanguages = {
+ es: {
+ 'es-419': 'es_419',
+ 'es-mx': 'es_419',
+ 'es-gt': 'es_419',
+ 'es-cr': 'es_419',
+ 'es-pa': 'es_419',
+ 'es-do': 'es_419',
+ 'es-ve': 'es_419',
+ 'es-co': 'es_419',
+ 'es-pe': 'es_419',
+ 'es-ar': 'es_419',
+ 'es-ec': 'es_419',
+ 'es-cl': 'es_419',
+ 'es-uy': 'es_419',
+ 'es-py': 'es_419',
+ 'es-bo': 'es_419',
+ 'es-sv': 'es_419',
+ 'es-hn': 'es_419',
+ 'es-ni': 'es_419',
+ 'es-pr': 'es_419',
+ },
+ zh: {
+ 'zh-tw': 'zh_TW',
+ },
+};
+
+// Export en strings only, temporary solution for mobile
+// This is copied from middlewares/locals#t()
+// TODO review if this can be removed since the old mobile app is no longer active
+// stringName and vars are the allowed parameters
+export function enTranslations (...args) {
+ let language = _.find(avalaibleLanguages, {code: 'en'});
+
+ // language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
+ args.push(language.code);
+ return shared.i18n.t(...args);
+}
diff --git a/website/src/libs/api-v3/logger.js b/website/src/libs/api-v3/logger.js
index 4583610d25..3ab20ad685 100644
--- a/website/src/libs/api-v3/logger.js
+++ b/website/src/libs/api-v3/logger.js
@@ -12,7 +12,9 @@ if (IS_PROD) {
// log errors to console too
} else {
logger
- .add(winston.transports.Console);
+ .add(winston.transports.Console, {
+ colorize: true,
+ });
}
export default logger;
diff --git a/website/src/middlewares/domain.js b/website/src/middlewares/api-v2/domain.js
similarity index 100%
rename from website/src/middlewares/domain.js
rename to website/src/middlewares/api-v2/domain.js
diff --git a/website/src/middlewares/errorHandler.js b/website/src/middlewares/api-v2/errorHandler.js
similarity index 100%
rename from website/src/middlewares/errorHandler.js
rename to website/src/middlewares/api-v2/errorHandler.js
diff --git a/website/src/middlewares/api-v3/domain.js b/website/src/middlewares/api-v3/domain.js
new file mode 100644
index 0000000000..63272381da
--- /dev/null
+++ b/website/src/middlewares/api-v3/domain.js
@@ -0,0 +1,16 @@
+// TODO in api-v2 this module also checked memory usage every x minutes and
+// threw an error in case of low memory avalible (possible memory leak)
+// it's yet to be decided whether to keep it or not
+import domainMiddleware from 'domain-middleware';
+
+export default function implementDomainMiddleware (server, mongoose) {
+ return domainMiddleware({
+ server: {
+ close () {
+ server.close();
+ mongoose.connection.close();
+ },
+ },
+ killTimeout: 10000,
+ });
+}
\ No newline at end of file
diff --git a/website/src/middlewares/api-v3/getUserLanguage.js b/website/src/middlewares/api-v3/getUserLanguage.js
new file mode 100644
index 0000000000..234a78a29c
--- /dev/null
+++ b/website/src/middlewares/api-v3/getUserLanguage.js
@@ -0,0 +1,79 @@
+import { model as User } from '../../models/user';
+import accepts from 'accepts';
+import _ from 'lodash';
+import {
+ translations,
+ defaultLangCodes,
+ multipleVersionsLanguages,
+} from '../../libs/api-v3/i18n';
+
+function _getUniqueListOfLanguages (languages) {
+ let acceptableLanguages = _(languages).map((lang) => {
+ return lang.slice(0, 2);
+ }).uniq().value();
+
+ let uniqueListOfLanguages = _.intersection(acceptableLanguages, defaultLangCodes);
+
+ return uniqueListOfLanguages;
+}
+
+function _checkForApplicableLanguageVariant (originalLanguageOptions) {
+ let languageVariant = _.find(originalLanguageOptions, (accepted) => {
+ let trimmedAccepted = accepted.slice(0, 2);
+
+ return multipleVersionsLanguages[trimmedAccepted];
+ });
+
+ return languageVariant;
+}
+
+function _getFromBrowser (req) {
+ let originalLanguageOptions = accepts(req).languages();
+ let uniqueListOfLanguages = _getUniqueListOfLanguages(originalLanguageOptions);
+ let baseLanguage = (uniqueListOfLanguages[0] || '').toLowerCase();
+ let languageMapping = multipleVersionsLanguages[baseLanguage];
+
+ if (languageMapping) {
+ let languageVariant = _checkForApplicableLanguageVariant(originalLanguageOptions);
+
+ if (languageVariant) {
+ languageVariant = languageVariant.toLowerCase();
+ } else {
+ return 'en';
+ }
+
+ return languageMapping[languageVariant] || baseLanguage;
+ } else {
+ return baseLanguage || 'en';
+ }
+}
+
+function _getFromUser (user, req) {
+ let preferredLang = user && user.preferences && user.preferences.language;
+ let lang = translations[preferredLang] ? preferredLang : _getFromBrowser(req);
+
+ return lang;
+}
+
+export default function getUserLanguage (req, res, next) {
+ if (req.query.lang) { // In case the language is specified in the request url, use it
+ req.language = translations[req.query.lang] ? req.query.lang : 'en';
+ return next();
+ } else if (req.locals && req.locals.user) { // If the request is authenticated, use the user's preferred language
+ req.language = _getFromUser(req.locals.user, req);
+ return next();
+ } else if (req.session && req.session.userId) { // Same thing if the user has a valid session
+ User.findOne({
+ _id: req.session.userId,
+ }, 'preferences.language')
+ .exec()
+ .then((user) => {
+ req.language = _getFromUser(user, req);
+ return next();
+ })
+ .catch(next);
+ } else { // Otherwise get from browser
+ req.language = _getFromUser(null, req);
+ return next();
+ }
+}
diff --git a/website/src/middlewares/locals.js b/website/src/middlewares/locals.js
index 2f238a6a99..223186a81a 100644
--- a/website/src/middlewares/locals.js
+++ b/website/src/middlewares/locals.js
@@ -1,9 +1,9 @@
var nconf = require('nconf');
var _ = require('lodash');
-var utils = require('../libs/utils');
+var utils = require('../libs/api-v2/utils');
var shared = require('../../../common');
-var i18n = require('../libs/i18n');
-var buildManifest = require('../libs/buildManifest');
+var i18n = require('../libs/api-v2/i18n');
+var buildManifest = require('../libs/api-v2/buildManifest');
var shared = require('../../../common');
var forceRefresh = require('./forceRefresh');
var tavernQuest = require('../models/group').tavernQuest;
diff --git a/website/src/models/group.js b/website/src/models/group.js
index b2da0011d7..0d3011cc38 100644
--- a/website/src/models/group.js
+++ b/website/src/models/group.js
@@ -6,7 +6,7 @@ var _ = require('lodash');
var async = require('async');
var logging = require('../libs/api-v2/logging');
var Challenge = require('./../models/challenge').model;
-var firebase = require('../libs/firebase');
+var firebase = require('../libs/api-v2/firebase');
// NOTE any change to groups' members in MongoDB will have to be run through the API
// changes made directly to the db will cause Firebase to get out of sync
diff --git a/website/src/routes/api-v2/auth.js b/website/src/routes/api-v2/auth.js
index c60f44547b..d76891e40d 100644
--- a/website/src/routes/api-v2/auth.js
+++ b/website/src/routes/api-v2/auth.js
@@ -1,6 +1,6 @@
var auth = require('../../controllers/api-v2/auth');
var express = require('express');
-var i18n = require('../../libs/i18n');
+var i18n = require('../../libs/api-v2/i18n');
var router = new express.Router();
/* auth.auth*/
diff --git a/website/src/routes/api-v2/coupon.js b/website/src/routes/api-v2/coupon.js
index 811d81a6f2..132184a585 100644
--- a/website/src/routes/api-v2/coupon.js
+++ b/website/src/routes/api-v2/coupon.js
@@ -3,7 +3,7 @@ var express = require('express');
var router = new express.Router();
var auth = require('../../controllers/api-v2/auth');
var coupon = require('../../controllers/api-v2/coupon');
-var i18n = require('../../libs/i18n');
+var i18n = require('../../libs/api-v2/i18n');
router.get('/api/v2/coupons', auth.authWithUrl, i18n.getUserLanguage, coupon.ensureAdmin, coupon.getCoupons);
router.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
index a1500ac39a..f167884245 100644
--- a/website/src/routes/api-v2/swagger.js
+++ b/website/src/routes/api-v2/swagger.js
@@ -18,7 +18,7 @@ var nconf = require("nconf");
var cron = user.cron;
var _ = require('lodash');
var content = require('../../../../common').content;
-var i18n = require('../../libs/i18n');
+var i18n = require('../../libs/api-v2/i18n');
var forceRefresh = require('../../middlewares/forceRefresh').middleware;
module.exports = function(swagger, v2) {
diff --git a/website/src/routes/api-v2/unsubscription.js b/website/src/routes/api-v2/unsubscription.js
index 942a396eef..3b31305b5a 100644
--- a/website/src/routes/api-v2/unsubscription.js
+++ b/website/src/routes/api-v2/unsubscription.js
@@ -1,6 +1,6 @@
var express = require('express');
var router = new express.Router();
-var i18n = require('../../libs/i18n');
+var i18n = require('../../libs/api-v2/i18n');
var unsubscription = require('../../controllers/api-v2/unsubscription');
router.get('/unsubscribe', i18n.getUserLanguage, unsubscription.unsubscribe);
diff --git a/website/src/routes/dataexport.js b/website/src/routes/dataexport.js
index 5bf02a228c..d7328434a0 100644
--- a/website/src/routes/dataexport.js
+++ b/website/src/routes/dataexport.js
@@ -3,7 +3,7 @@ var router = new express.Router();
var dataexport = require('../controllers/dataexport');
var auth = require('../controllers/api-v2/auth');
var nconf = require('nconf');
-var i18n = require('../libs/i18n');
+var i18n = require('../libs/api-v2/i18n');
var locals = require('../middlewares/locals');
/* Data export */
diff --git a/website/src/routes/pages.js b/website/src/routes/pages.js
index 27bc9a3619..7c847722e7 100644
--- a/website/src/routes/pages.js
+++ b/website/src/routes/pages.js
@@ -3,7 +3,7 @@ var express = require('express');
var router = new express.Router();
var _ = require('lodash');
var locals = require('../middlewares/locals');
-var i18n = require('../libs/i18n');
+var i18n = require('../libs/api-v2/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 41c03210be..4989b113a1 100644
--- a/website/src/routes/payments.js
+++ b/website/src/routes/payments.js
@@ -3,7 +3,7 @@ var express = require('express');
var router = new express.Router();
var auth = require('../controllers/api-v2/auth');
var payments = require('../controllers/payments');
-var i18n = require('../libs/i18n');
+var i18n = require('../libs/api-v2/i18n');
router.get('/paypal/checkout', auth.authWithUrl, i18n.getUserLanguage, payments.paypalCheckout);
router.get('/paypal/checkout/success', i18n.getUserLanguage, payments.paypalCheckoutSuccess);
diff --git a/website/src/server.js b/website/src/server.js
index 2562c9bb87..1142f27539 100644
--- a/website/src/server.js
+++ b/website/src/server.js
@@ -2,7 +2,6 @@
import nconf from 'nconf';
import logger from './libs/api-v3/logger';
-import utils from './libs/utils';
import express from 'express';
import http from 'http';
// import path from 'path';
@@ -13,11 +12,11 @@ import passport from 'passport';
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';
-utils.setupConfig();
// Setup translations
-// let i18n = require('./libs/i18n');
+// let i18n = require('./libs/api-v2/i18n');
const IS_PROD = nconf.get('IS_PROD');
// const IS_DEV = nconf.get('IS_DEV');
@@ -42,7 +41,7 @@ let db = mongoose.connect(nconf.get('NODE_DB_URI'), mongooseOptions, (err) => {
autoinc.init(db);
-import './libs/firebase';
+import './libs/api-v3/firebase';
// load schemas & models
import './models/challenge';
@@ -84,6 +83,7 @@ app.set('port', nconf.get('PORT'));
let oldApp = express(); // api v1 and v2, and not scoped routes
let 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);
@@ -95,7 +95,7 @@ attachMiddlewares(newApp);
/* OLD APP IS DISABLED UNTIL COMPATIBLE WITH NEW MODELS
//require('./middlewares/apiThrottle')(oldApp);
-oldApp.use(require('./middlewares/domain')(server,mongoose));
+oldApp.use(require('./middlewares/api-v2/domain')(server,mongoose));
if (!IS_PROD && !DISABLE_LOGGING) oldApp.use(require('morgan')("dev"));
oldApp.use(require('compression')());
oldApp.set("views", __dirname + "/../views");
@@ -153,7 +153,7 @@ oldApp.use('/common/script/public', express['static'](publicDir + "/../../common
oldApp.use('/common/img', express['static'](publicDir + "/../../common/img", { maxAge: maxAge }));
oldApp.use(express['static'](publicDir));
-oldApp.use(require('./middlewares/errorHandler'));
+oldApp.use(require('./middlewares/api-v2/errorHandler'));
*/
server.on('request', app);