allow clients to filter content api call

This commit is contained in:
Phillip Thelen
2024-01-25 16:16:49 +01:00
committed by Sabe Jones
parent ed790c1c4d
commit 39252c7828
2 changed files with 80 additions and 9 deletions

View File

@@ -1,4 +1,5 @@
import nconf from 'nconf'; import nconf from 'nconf';
import fs from 'fs';
import { langCodes } from '../../libs/i18n'; import { langCodes } from '../../libs/i18n';
import { CONTENT_CACHE_PATH, getLocalizedContentResponse } from '../../libs/content'; import { CONTENT_CACHE_PATH, getLocalizedContentResponse } from '../../libs/content';
@@ -6,6 +7,28 @@ const IS_PROD = nconf.get('IS_PROD');
const api = {}; const api = {};
const CACHED_HASHES = [
];
const MOBILE_FILTER = `achievements,questSeriesAchievements,animalColorAchievements,animalSetAchievements,stableAchievements,
mystery,bundles,loginIncentives,pets,premiumPets,specialPets,questPets,wackyPets,mounts,premiumMounts,specialMounts,questMounts,
events,dropEggs,questEggs,dropHatchingPotions,premiumHatchingPotions,wackyHatchingPotions,backgroundsFlat,questsByLevel,gear.tree,
tasksByCategory,userDefaults,timeTravelStable,gearTypes,cardTypes`;
function hashForFilter (filter) {
let hash = 0;
let i; let
chr;
if (filter.length === 0) return '';
for (i = 0; i < filter.length; i++) { // eslint-disable-line
chr = filter.charCodeAt(i);
hash = ((hash << 5) - hash) + chr; // eslint-disable-line
hash |= 0; // eslint-disable-line
}
return String(hash);
}
/** /**
* @api {get} /api/v3/content Get all available content objects * @api {get} /api/v3/content Get all available content objects
* @apiDescription Does not require authentication. * @apiDescription Does not require authentication.
@@ -65,14 +88,54 @@ api.getContent = {
language = proposedLang; language = proposedLang;
} }
let filter = req.query.filter || '';
// apply defaults for mobile clients
if (filter === '') {
if (req.headers['x-client'] === 'habitica-android') {
filter = `${MOBILE_FILTER},appearance.background`;
} else if (req.headers['x-client'] === 'habitica-ios') {
filter = `${MOBILE_FILTER},backgrounds`;
}
}
// Build usable filter object
const filterObj = {};
filter.split(',').forEach(item => {
if (item.includes('.')) {
const [key, subkey] = item.split('.');
if (!filterObj[key]) {
filterObj[key] = {};
}
filterObj[key][subkey.trim()] = true;
} else {
filterObj[item.trim()] = true;
}
});
if (IS_PROD) { if (IS_PROD) {
res.sendFile(`${CONTENT_CACHE_PATH}${language}.json`); const filterHash = language + hashForFilter(filter);
if (CACHED_HASHES.includes(filterHash)) {
// Content is already cached, so just send it.
res.sendFile(`${CONTENT_CACHE_PATH}${filterHash}.json`);
} else {
// Content is not cached, so cache it and send it.
res.set({
'Content-Type': 'application/json',
});
const jsonResString = getLocalizedContentResponse(language, filterObj);
fs.writeFileSync(
`${CONTENT_CACHE_PATH}${filterHash}.json`,
jsonResString,
'utf8',
);
CACHED_HASHES.push(filterHash);
res.status(200).send(jsonResString);
}
} else { } else {
res.set({ res.set({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}); });
const jsonResString = getLocalizedContentResponse(language, filterObj);
const jsonResString = getLocalizedContentResponse(language);
res.status(200).send(jsonResString); res.status(200).send(jsonResString);
} }
}, },

View File

@@ -5,23 +5,31 @@ import packageInfo from '../../../package.json';
export const CONTENT_CACHE_PATH = path.join(__dirname, '/../../../content_cache/'); export const CONTENT_CACHE_PATH = path.join(__dirname, '/../../../content_cache/');
function walkContent (obj, lang) { function walkContent (obj, lang, removedKeys = {}) {
_.each(obj, (item, key, source) => { _.each(obj, (item, key, source) => {
if (key in removedKeys && removedKeys[key] === true) {
delete source[key];
return;
}
if (_.isPlainObject(item) || _.isArray(item)) { if (_.isPlainObject(item) || _.isArray(item)) {
walkContent(item, lang); if (key in removedKeys && _.isPlainObject(removedKeys[key])) {
walkContent(item, lang, removedKeys[key]);
} else {
walkContent(item, lang);
}
} else if (_.isFunction(item) && item.i18nLangFunc) { } else if (_.isFunction(item) && item.i18nLangFunc) {
source[key] = item(lang); source[key] = item(lang);
} }
}); });
} }
export function localizeContentData (data, langCode) { export function localizeContentData (data, langCode, removedKeys = {}) {
const dataClone = _.cloneDeep(data); const dataClone = _.cloneDeep(data);
walkContent(dataClone, langCode); walkContent(dataClone, langCode, removedKeys);
return dataClone; return dataClone;
} }
export function getLocalizedContentResponse (langCode) { export function getLocalizedContentResponse (langCode, removedKeys = {}) {
const localizedContent = localizeContentData(common.content, langCode); const localizedContent = localizeContentData(common.content, langCode, removedKeys);
return `{"success": true, "data": ${JSON.stringify(localizedContent)}, "appVersion": "${packageInfo.version}"}`; return `{"success": true, "data": ${JSON.stringify(localizedContent)}, "appVersion": "${packageInfo.version}"}`;
} }