mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 22:27:26 +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`
|
||||
// code which in production works only if transpiled so after
|
||||
// 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
|
||||
|
||||
try {
|
||||
@@ -23,7 +23,7 @@ gulp.task('content:cache', done => {
|
||||
langCodes.forEach(langCode => {
|
||||
fs.writeFileSync(
|
||||
`${CONTENT_CACHE_PATH}${langCode}.json`,
|
||||
getLocalizedContent(langCode),
|
||||
getLocalizedContentResponse(langCode),
|
||||
'utf8',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -8,9 +8,9 @@ describe('contentLib', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLocalizedContent', () => {
|
||||
describe('getLocalizedContentResponse', () => {
|
||||
it('clones, not modify, the original content data', () => {
|
||||
contentLib.getLocalizedContent();
|
||||
contentLib.getLocalizedContentResponse();
|
||||
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"',
|
||||
|
||||
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 { 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');
|
||||
|
||||
@@ -72,7 +72,7 @@ api.getContent = {
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
|
||||
const jsonResString = getLocalizedContent(language);
|
||||
const jsonResString = getLocalizedContentResponse(language);
|
||||
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) {
|
||||
const contentClone = _.cloneDeep(common.content);
|
||||
walkContent(contentClone, langCode);
|
||||
return `{"success": true, "data": ${JSON.stringify(contentClone)}}`;
|
||||
export function localizeContentData (data, langCode) {
|
||||
const dataClone = _.cloneDeep(data);
|
||||
walkContent(dataClone, langCode);
|
||||
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