mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 06:37:23 +01:00
* feat(api-v4): new /faq route added * refactor(server): change of function name in libs/content.js
This commit is contained in:
@@ -6,7 +6,7 @@ gulp.task('content:cache', done => {
|
|||||||
// Requiring at runtime because these files access `common`
|
// Requiring at runtime because these files access `common`
|
||||||
// code which in production works only if transpiled so after
|
// code which in production works only if transpiled so after
|
||||||
// gulp build:babel:common has run
|
// gulp build:babel:common has run
|
||||||
const { CONTENT_CACHE_PATH, getLocalizedContent } = require('../website/server/libs/content'); // eslint-disable-line global-require
|
const { CONTENT_CACHE_PATH, getLocalizedContentResponse } = require('../website/server/libs/content'); // eslint-disable-line global-require
|
||||||
const { langCodes } = require('../website/server/libs/i18n'); // eslint-disable-line global-require
|
const { langCodes } = require('../website/server/libs/i18n'); // eslint-disable-line global-require
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -23,7 +23,7 @@ gulp.task('content:cache', done => {
|
|||||||
langCodes.forEach(langCode => {
|
langCodes.forEach(langCode => {
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
`${CONTENT_CACHE_PATH}${langCode}.json`,
|
`${CONTENT_CACHE_PATH}${langCode}.json`,
|
||||||
getLocalizedContent(langCode),
|
getLocalizedContentResponse(langCode),
|
||||||
'utf8',
|
'utf8',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ describe('contentLib', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getLocalizedContent', () => {
|
describe('getLocalizedContentResponse', () => {
|
||||||
it('clones, not modify, the original content data', () => {
|
it('clones, not modify, the original content data', () => {
|
||||||
contentLib.getLocalizedContent();
|
contentLib.getLocalizedContentResponse();
|
||||||
expect(typeof content.backgrounds.backgrounds062014.beach.text).to.equal('function');
|
expect(typeof content.backgrounds.backgrounds062014.beach.text).to.equal('function');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
22
test/api/unit/libs/localizeContentData.js
Normal file
22
test/api/unit/libs/localizeContentData.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import faq from '../../../../website/common/script/content/faq';
|
||||||
|
import common from '../../../../website/common';
|
||||||
|
import { localizeContentData } from '../../../../website/server/libs/content';
|
||||||
|
|
||||||
|
const { i18n } = common;
|
||||||
|
|
||||||
|
describe('localizeContentData', () => {
|
||||||
|
it('Should take a an object with localization identifiers and '
|
||||||
|
+ 'return an object with actual translations in English', () => {
|
||||||
|
const faqInEnglish = localizeContentData(faq, 'en');
|
||||||
|
|
||||||
|
expect(faqInEnglish).to.have.property('stillNeedHelp');
|
||||||
|
expect(faqInEnglish.stillNeedHelp.ios).to.equal(i18n.t('iosFaqStillNeedHelp', 'en'));
|
||||||
|
});
|
||||||
|
it('Should take an object with localization identifiers and '
|
||||||
|
+ 'return an object with actual translations in German', () => {
|
||||||
|
const faqInEnglish = localizeContentData(faq, 'de');
|
||||||
|
|
||||||
|
expect(faqInEnglish).to.have.property('stillNeedHelp');
|
||||||
|
expect(faqInEnglish.stillNeedHelp.ios).to.equal(i18n.t('iosFaqStillNeedHelp', 'de'));
|
||||||
|
});
|
||||||
|
});
|
||||||
65
test/api/v4/faq/GET-faq.test.js
Normal file
65
test/api/v4/faq/GET-faq.test.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import {
|
||||||
|
requester,
|
||||||
|
translate,
|
||||||
|
} from '../../../helpers/api-integration/v4';
|
||||||
|
import i18n from '../../../../website/common/script/i18n';
|
||||||
|
|
||||||
|
describe('GET /faq', () => {
|
||||||
|
describe('language parameter', () => {
|
||||||
|
it('returns faq (and does not require authentication)', async () => {
|
||||||
|
const res = await requester().get('/faq');
|
||||||
|
|
||||||
|
expect(res).to.have.property('stillNeedHelp');
|
||||||
|
expect(res.stillNeedHelp.ios).to.equal(translate('iosFaqStillNeedHelp'));
|
||||||
|
expect(res).to.have.property('questions');
|
||||||
|
expect(res.questions[0].question).to.equal(translate('faqQuestion0'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns faq not in English', async () => {
|
||||||
|
const res = await requester().get('/faq?language=de');
|
||||||
|
expect(res).to.have.nested.property('stillNeedHelp.ios');
|
||||||
|
expect(res.stillNeedHelp.ios).to.equal(i18n.t('iosFaqStillNeedHelp', 'de'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to English if the desired language is not found', async () => {
|
||||||
|
const res = await requester().get('/faq?language=wrong');
|
||||||
|
expect(res).to.have.nested.property('stillNeedHelp.ios');
|
||||||
|
expect(res.stillNeedHelp.ios).to.equal(translate('iosFaqStillNeedHelp'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('platform parameter', () => {
|
||||||
|
it('returns faq with answers for ios platform only', async () => {
|
||||||
|
const res = await requester().get('/faq?platform=ios');
|
||||||
|
|
||||||
|
expect(res).to.have.property('stillNeedHelp');
|
||||||
|
expect(res.stillNeedHelp).to.eql({ ios: translate('iosFaqStillNeedHelp') });
|
||||||
|
|
||||||
|
expect(res).to.have.property('questions');
|
||||||
|
expect(res.questions[0]).to.eql({
|
||||||
|
question: translate('faqQuestion0'),
|
||||||
|
ios: translate('iosFaqAnswer0'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns an error when invalid platform parameter is specified', async () => {
|
||||||
|
const request = requester().get('/faq?platform=wrong');
|
||||||
|
await expect(request)
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: i18n.t('invalidReqParams'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('falls back to "web" description if there is no description for specified platform', async () => {
|
||||||
|
const res = await requester().get('/faq?platform=android');
|
||||||
|
expect(res).to.have.property('stillNeedHelp');
|
||||||
|
expect(res.stillNeedHelp).to.eql({ web: translate('webFaqStillNeedHelp') });
|
||||||
|
|
||||||
|
expect(res).to.have.property('questions');
|
||||||
|
expect(res.questions[0]).to.eql({
|
||||||
|
question: translate('faqQuestion0'),
|
||||||
|
android: translate('androidFaqAnswer0'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -27,4 +27,6 @@ export default {
|
|||||||
missingSubKey: 'Missing "req.query.sub"',
|
missingSubKey: 'Missing "req.query.sub"',
|
||||||
|
|
||||||
ipAddressBlocked: 'This IP address has been blocked from accessing Habitica. This may be due to a breach of our Terms of Service or technical issue originating at this IP address. For details or to ask to be unblocked, please email admin@habitica.com or ask your parent or guardian to email them. Include your Habitica @ Username or User Id in the email if you have one.',
|
ipAddressBlocked: 'This IP address has been blocked from accessing Habitica. This may be due to a breach of our Terms of Service or technical issue originating at this IP address. For details or to ask to be unblocked, please email admin@habitica.com or ask your parent or guardian to email them. Include your Habitica @ Username or User Id in the email if you have one.',
|
||||||
|
|
||||||
|
invalidPlatform: 'Invalid platform specified',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import { langCodes } from '../../libs/i18n';
|
import { langCodes } from '../../libs/i18n';
|
||||||
import { CONTENT_CACHE_PATH, getLocalizedContent } from '../../libs/content';
|
import { CONTENT_CACHE_PATH, getLocalizedContentResponse } from '../../libs/content';
|
||||||
|
|
||||||
const IS_PROD = nconf.get('IS_PROD');
|
const IS_PROD = nconf.get('IS_PROD');
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ api.getContent = {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
});
|
});
|
||||||
|
|
||||||
const jsonResString = getLocalizedContent(language);
|
const jsonResString = getLocalizedContentResponse(language);
|
||||||
res.status(200).send(jsonResString);
|
res.status(200).send(jsonResString);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
77
website/server/controllers/api-v4/faq.js
Normal file
77
website/server/controllers/api-v4/faq.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import { query } from 'express-validator/check';
|
||||||
|
import { langCodes } from '../../libs/i18n';
|
||||||
|
import apiError from '../../libs/apiError';
|
||||||
|
import common from '../../../common';
|
||||||
|
import { localizeContentData } from '../../libs/content';
|
||||||
|
|
||||||
|
const { content } = common;
|
||||||
|
const { faq } = content;
|
||||||
|
|
||||||
|
const api = {};
|
||||||
|
|
||||||
|
function _deleteProperties (obj, keysToDelete, platform) {
|
||||||
|
// if there is no description for specified platform, use 'web' description by default
|
||||||
|
if (obj[platform] === undefined) {
|
||||||
|
delete obj.ios;
|
||||||
|
delete obj.android;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
keysToDelete.forEach(key => delete obj[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _deleteOtherPlatformsAnswers (faqObject, platform) {
|
||||||
|
const faqCopy = _.cloneDeep(faqObject);
|
||||||
|
const keysToDelete = _.without(['web', 'ios', 'android'], platform);
|
||||||
|
|
||||||
|
_deleteProperties(faqCopy.stillNeedHelp, keysToDelete, platform);
|
||||||
|
faqCopy.questions.forEach(question => {
|
||||||
|
_deleteProperties(question, keysToDelete, platform);
|
||||||
|
});
|
||||||
|
|
||||||
|
return faqCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /api/v4/faq Get faq in json format
|
||||||
|
* @apiDescription Does not require authentication.
|
||||||
|
* @apiName FaqGet
|
||||||
|
* @apiGroup Content
|
||||||
|
*
|
||||||
|
* @apiParam (Query) {String="bg","cs","da","de",
|
||||||
|
* "en","en@pirate","en_GB",
|
||||||
|
* "es","es_419","fr","he","hu",
|
||||||
|
* "id","it","ja","nl","pl","pt","pt_BR",
|
||||||
|
* "ro","ru","sk","sr","sv",
|
||||||
|
* "uk","zh","zh_TW"} [language=en] Language code used for the items'
|
||||||
|
* strings. If the authenticated user makes
|
||||||
|
* the request, the content will return with
|
||||||
|
* the user's configured language.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object} data FAQ in a json format
|
||||||
|
*/
|
||||||
|
api.faq = {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/faq',
|
||||||
|
middlewares: [
|
||||||
|
query('platform')
|
||||||
|
.optional()
|
||||||
|
.isIn(['web', 'android', 'ios']).withMessage(apiError('invalidPlatform')),
|
||||||
|
],
|
||||||
|
async handler (req, res) {
|
||||||
|
const validationErrors = req.validationErrors();
|
||||||
|
if (validationErrors) throw validationErrors;
|
||||||
|
|
||||||
|
const proposedLang = req.query.language && req.query.language.toString();
|
||||||
|
const language = langCodes.includes(proposedLang) ? proposedLang : 'en';
|
||||||
|
|
||||||
|
const { platform } = req.query;
|
||||||
|
|
||||||
|
const dataToLocalize = platform ? _deleteOtherPlatformsAnswers(faq, platform) : faq;
|
||||||
|
res.respond(200, localizeContentData(dataToLocalize, language));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default api;
|
||||||
@@ -14,8 +14,13 @@ function walkContent (obj, lang) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLocalizedContent (langCode) {
|
export function localizeContentData (data, langCode) {
|
||||||
const contentClone = _.cloneDeep(common.content);
|
const dataClone = _.cloneDeep(data);
|
||||||
walkContent(contentClone, langCode);
|
walkContent(dataClone, langCode);
|
||||||
return `{"success": true, "data": ${JSON.stringify(contentClone)}}`;
|
return dataClone;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLocalizedContentResponse (langCode) {
|
||||||
|
const localizedContent = localizeContentData(common.content, langCode);
|
||||||
|
return `{"success": true, "data": ${JSON.stringify(localizedContent)}}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user