port locals middleware, add some tests and a lot of fixes

This commit is contained in:
Matteo Pagliazzi
2016-02-19 18:42:03 +01:00
parent feadfbcd9e
commit cc31d266e4
18 changed files with 158 additions and 18 deletions

View File

@@ -0,0 +1,35 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('GET /export/avatar-:memberId.html', () => {
let user;
before(async () => {
user = await generateUser();
});
it('validates req.params.memberId', async () => {
await expect(user.get(`/export/avatar-:memberId.html`)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('handles non-existing members', async () => {
let dummyId = generateUUID();
await expect(user.get(`/export/avatar-${dummyId}.html`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('userWithIDNotFound', {userId: dummyId}),
});
});
it('returns an html page', async () => {
let res = await user.get(`/export/avatar-${user._id}.html`);
expect(res.substring(0, 100).indexOf('<!DOCTYPE html>')).to.equal(0);
});
});

View File

@@ -0,0 +1,3 @@
// TODO how to test this route since it points to a file on AWS s3?
describe('GET /export/avatar-:memberId.png', () => {});

View File

@@ -0,0 +1,3 @@
// TODO how to test this route since it uses session authentication?
describe('GET /export/history.csv', () => {});

View File

@@ -0,0 +1,3 @@
// TODO how to test this route since it uses session authentication?
describe('GET /export/userdata.json', () => {});

View File

@@ -0,0 +1,3 @@
// TODO how to test this route since it uses session authentication?
describe('GET /export/userdata.xml', () => {});

View File

@@ -7,8 +7,8 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl, Social) {
$scope.loadWidgets = Social.loadWidgets;
if(env.isStaticPage){
$scope.languages = env.avalaibleLanguages;
$scope.selectedLanguage = _.find(env.avalaibleLanguages, {code: env.language.code});
$scope.languages = env.availableLanguages;
$scope.selectedLanguage = _.find(env.availableLanguages, {code: env.language.code});
$rootScope.selectedLanguage = $scope.selectedLanguage;

View File

@@ -84,7 +84,7 @@ habitrpg.controller('SettingsCtrl',
};
$scope.language = window.env.language;
$scope.avalaibleLanguages = window.env.avalaibleLanguages;
$scope.availableLanguages = window.env.availableLanguages;
$scope.changeLanguage = function(){
$rootScope.$on('userSynced', function(){

View File

@@ -14,6 +14,7 @@ import AWS from 'aws-sdk';
import nconf from 'nconf';
import got from 'got';
import Q from 'q';
import locals from '../../middlewares/api-v3/locals';
let S3 = new AWS.S3({
accessKeyId: nconf.get('S3:accessKeyId'),
@@ -25,6 +26,8 @@ const BASE_URL = nconf.get('BASE_URL');
let api = {};
// TODO move these routes out of the /api/v3/export namespace to the top level /export
/**
* @api {get} /export/history.csv Export user tasks history in CSV format. History is only available for habits and dailys so todos and rewards won't be included
* @apiVersion 3.0.0
@@ -152,6 +155,7 @@ api.exportUserDataXml = {
api.exportUserAvatarHtml = {
method: 'GET',
url: '/export/avatar-:memberId.html',
middlewares: [locals],
async handler (req, res) {
req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();

View File

@@ -131,7 +131,7 @@ function _getMembersForItem (type) {
*
* @apiParam {UUID} groupId The group id
* @apiParam {UUID} lastId Query parameter to specify the last member returned in a previous request to this route and get the next batch of results
* @apiParam {boolean} includeAllPublicFields Query parameter avalaible only when fetching a party. If === `true` then all public fields for members will be returned (liek when making a request for a single member)
* @apiParam {boolean} includeAllPublicFields Query parameter available only when fetching a party. If === `true` then all public fields for members will be returned (liek when making a request for a single member)
*
* @apiSuccess {array} members An array of members, sorted by _id
*/

View File

@@ -49,7 +49,7 @@ shared.i18n.translations = translations;
export let langCodes = Object.keys(translations);
export let avalaibleLanguages = langCodes.map((langCode) => {
export let availableLanguages = langCodes.map((langCode) => {
return {
code: langCode,
name: translations[langCode].languageName,
@@ -57,7 +57,7 @@ export let avalaibleLanguages = langCodes.map((langCode) => {
});
langCodes.forEach((code) => {
let lang = _.find(avalaibleLanguages, {code});
let lang = _.find(availableLanguages, {code});
lang.momentLangCode = momentLangsMapping[code] || code;
@@ -110,7 +110,7 @@ export let multipleVersionsLanguages = {
// 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'});
let language = _.find(availableLanguages, {code: 'en'});
// language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
args.push(language.code);

View File

@@ -27,6 +27,8 @@ export default function attachMiddlewares (app) {
app.use(setupBody);
app.use(responseHandler);
app.use(getUserLanguage);
app.set('view engine', 'jade');
app.set('views', `${__dirname}/../../../views`);
app.use('/api/v3', routes);
app.use(notFoundHandler);

View File

@@ -0,0 +1,73 @@
import nconf from 'nconf';
import _ from 'lodash';
import shared from '../../../../common';
import * as i18n from '../../libs/api-v3/i18n';
import {
getBuildUrl,
getManifestFiles,
} from '../../libs/api-v3/buildManifest';
import forceRefresh from './../forceRefresh';
import { tavernQuest } from '../../models/group';
import { mods } from '../../models/user';
import { decrypt } from '../../libs/api-v3/encryption';
// To avoid stringifying more data then we need,
// items from `env` used on the client will have to be specified in this array
// TODO where is this used?
const CLIENT_VARS = ['language', 'isStaticPage', 'availableLanguages', 'translations',
'FACEBOOK_KEY', 'NODE_ENV', 'BASE_URL', 'GA_ID',
'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY',
'worldDmg', 'mods', 'IS_MOBILE'];
let env = {
getManifestFiles,
getBuildUrl,
_,
clientVars: CLIENT_VARS,
mods,
Content: shared.content,
siteVersion: forceRefresh.siteVersion,
availableLanguages: i18n.available,
AMAZON_PAYMENTS: {
SELLER_ID: nconf.get('AMAZON_PAYMENTS:SELLER_ID'),
CLIENT_ID: nconf.get('AMAZON_PAYMENTS:CLIENT_ID'),
},
};
'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY AMPLITUDE_KEY'.split(' ').forEach(key => {
env[key] = nconf.get(key);
});
export default function locals (req, res, next) {
let language = _.find(i18n.availableLanguages, {code: req.language});
let isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/'
// Load moment.js language file only when not on static pages
language.momentLang = !isStaticPage && i18n.momentLangs[language.code] || undefined;
res.locals.habitrpg = _.assign(env, {
IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
language,
isStaticPage,
translations: i18n.translations[language.code],
t (...args) { // stringName and vars are the allowed parameters
args.push(language.code);
return shared.i18n.t(...args);
},
// Defined here and not outside of the middleware because tavernQuest might be an
// empty object until the query to fetch it finishes
worldDmg: tavernQuest && tavernQuest.extra && tavernQuest.extra.worldDmg || {},
});
// Put query-string party (& guild but use partyInvite for backward compatibility)
// invitations into session to be handled later
if (req.query.partyInvite) {
try {
req.session.partyInvite = JSON.parse(decrypt(req.query.partyInvite));
} catch (e) {
// TODO logs
}
}
next();
}

View File

@@ -489,7 +489,7 @@ schema.statics.bossQuest = async function bossQuest (user, progress) {
export let tavernQuest = {};
let tavernQ = {_id: 'habitrpg', 'quest.key': {$ne: null}};
// we use process.nextTick because at this point the model is not yet avalaible
// we use process.nextTick because at this point the model is not yet available
process.nextTick(() => {
model // eslint-disable-line no-use-before-define
.findOne(tavernQ).exec()

View File

@@ -6,11 +6,24 @@ var nconf = require('nconf');
var i18n = require('../libs/api-v2/i18n');
var locals = require('../middlewares/locals');
/* Data export */
router.get('/history.csv',auth.authWithSession,i18n.getUserLanguage,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes
router.get('/userdata.xml',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.xml);
router.get('/userdata.json',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.json);
router.get('/avatar-:uuid.html', i18n.getUserLanguage, locals, dataexport.avatarPage);
router.get('/avatar-:uuid.png', i18n.getUserLanguage, locals, dataexport.avatarImage);
const BASE_URL = nconf.get('BASE_URL');
/* Data export deprecated routes */
// TODO remove once api v2 is taken down
router.get('/history.csv', (req, res) => {
res.redirect(`${BASE_URL}/api/v3/export/history.csv`);
});
router.get('/userdata.xml', (req, res) => {
res.redirect(`${BASE_URL}/api/v3/export/userdata.xml`);
});
router.get('/userdata.json', (req, res) => {
res.redirect(`${BASE_URL}/api/v3/export/userdata.json`);
});
router.get('/avatar-:uuid.html', (req, res) => {
res.redirect(`${BASE_URL}/api/v3/export/avatar-${req.params.uuid}.html`);
});
router.get('/avatar-:uuid.png', (req, res) => {
res.redirect(`${BASE_URL}/api/v3/export/avatar-${req.params.uuid}.png`);
});
module.exports = router;

View File

@@ -61,7 +61,6 @@ let FacebookStrategy = passportFacebook.Strategy;
// 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
@@ -91,7 +90,9 @@ app.all(/^(?!\/api\/v3).+/i, oldApp);
// Matches all requests going to /api/v3
app.all('/api/*', newApp);
// Mount middlewares for the new app
// 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

View File

@@ -9,7 +9,7 @@ html(ng-app="habitrpg")
meta(name='apple-mobile-web-app-capable', content='yes')
// .slice(0).push('user') is to clone the array,
// to be surethat `user` is never avalaible to other requests' env
// to be surethat `user` is never available to other requests' env
// TODO does it need only `user` in clientVars, not the others?
-
clientVars = env.clientVars.slice(0);

View File

@@ -32,7 +32,7 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
.form-horizontal
h5=env.t('language')
select.form-control(ng-model='language.code', ng-options='lang.code as lang.name for lang in avalaibleLanguages', ng-change='changeLanguage()')
select.form-control(ng-model='language.code', ng-options='lang.code as lang.name for lang in availableLanguages', ng-change='changeLanguage()')
small
!=env.t('americanEnglishGovern')
br