mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-03-13 08:41:14 +08:00
* build cached content files for mobile during gulp build * load already cached content files during startup * add option for mongoose to define minPoolSize * cache client index.html for 10 minutes. Improves initial load times * add option to auth to use lean version of user doc * add a way to produce a heapdump from the command line * fix lint
155 lines
4.8 KiB
JavaScript
155 lines
4.8 KiB
JavaScript
import _ from 'lodash';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
import moment from 'moment';
|
|
import nconf from 'nconf';
|
|
import common from '../../common';
|
|
import packageInfo from '../../../package.json';
|
|
|
|
export const CONTENT_CACHE_PATH = path.join(__dirname, '/../../../content_cache/');
|
|
const SWITCHOVER_TIME = nconf.get('CONTENT_SWITCHOVER_TIME_OFFSET') || 0;
|
|
|
|
const MOBILE_FILTER = ['achievements', 'questSeriesAchievements', 'animalColorAchievements', 'animalSetAchievements',
|
|
'stableAchievements', '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'];
|
|
|
|
export const ANDROID_FILTER = [...MOBILE_FILTER, 'appearances.background'].join(',');
|
|
export const IOS_FILTER = [...MOBILE_FILTER, 'backgrounds'].join(',');
|
|
|
|
function getDay (date) {
|
|
if (date === undefined) {
|
|
return 0;
|
|
}
|
|
const checkDate = new Date(date.getTime());
|
|
checkDate.setHours(checkDate.getHours() - SWITCHOVER_TIME);
|
|
return checkDate.getDate();
|
|
}
|
|
|
|
function getMonth (date) {
|
|
if (date === undefined) {
|
|
return 0;
|
|
}
|
|
return date instanceof moment ? date.month() : date.getMonth();
|
|
}
|
|
|
|
let CACHED_DATE = null;
|
|
let CACHED_HASHES = [
|
|
|
|
];
|
|
|
|
// Load existing cached hashes
|
|
try {
|
|
const files = fs.readdirSync(CONTENT_CACHE_PATH);
|
|
files.forEach(file => {
|
|
if (file.endsWith('.json')) {
|
|
const fileName = file.substring(0, file.length - 5);
|
|
CACHED_HASHES.push(fileName);
|
|
}
|
|
});
|
|
if (CACHED_HASHES.length > 0) {
|
|
CACHED_DATE = new Date();
|
|
}
|
|
} catch (err) {
|
|
// Folder does not exist yet
|
|
}
|
|
|
|
function walkContent (obj, lang, removedKeys = {}) {
|
|
_.each(obj, (item, key, source) => {
|
|
if (key in removedKeys && removedKeys[key] === true) {
|
|
delete source[key];
|
|
return;
|
|
}
|
|
if (_.isPlainObject(item) || _.isArray(item)) {
|
|
if (key in removedKeys && _.isPlainObject(removedKeys[key])) {
|
|
walkContent(item, lang, removedKeys[key]);
|
|
} else {
|
|
walkContent(item, lang);
|
|
}
|
|
} else if (_.isFunction(item) && item.i18nLangFunc) {
|
|
source[key] = item(lang);
|
|
}
|
|
});
|
|
}
|
|
|
|
export function localizeContentData (data, langCode, removedKeys = {}) {
|
|
const dataClone = _.cloneDeep(data);
|
|
walkContent(dataClone, langCode, removedKeys);
|
|
return dataClone;
|
|
}
|
|
|
|
export function getLocalizedContentResponse (langCode, removedKeys = {}) {
|
|
const localizedContent = localizeContentData(common.content, langCode, removedKeys);
|
|
return `{"success": true, "data": ${JSON.stringify(localizedContent)}, "appVersion": "${packageInfo.version}"}`;
|
|
}
|
|
|
|
export 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);
|
|
}
|
|
|
|
export function buildFilterObject (filter) {
|
|
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;
|
|
}
|
|
});
|
|
return filterObj;
|
|
}
|
|
|
|
export function serveContent (res, language, filter, isProd) {
|
|
// Build usable filter object
|
|
const filterObj = buildFilterObject(filter);
|
|
if (isProd) {
|
|
const today = new Date();
|
|
if (CACHED_DATE && (getDay(today) !== getDay(CACHED_DATE)
|
|
|| getMonth(today) !== getMonth(CACHED_DATE))) {
|
|
// Clear cached results, since they are old
|
|
CACHED_HASHES = [];
|
|
CACHED_DATE = undefined;
|
|
}
|
|
const cachedName = language + hashForFilter(filter);
|
|
if (CACHED_HASHES.includes(cachedName)) {
|
|
// Content is already cached, so just send it.
|
|
res.sendFile(`${CONTENT_CACHE_PATH}${cachedName}.json`);
|
|
} else {
|
|
console.log(`Caching content for language ${language} with filter ${filter}`);
|
|
// 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}${cachedName}.json`,
|
|
jsonResString,
|
|
'utf8',
|
|
);
|
|
CACHED_HASHES.push(cachedName);
|
|
CACHED_DATE = new Date();
|
|
res.status(200).send(jsonResString);
|
|
}
|
|
} else {
|
|
res.set({
|
|
'Content-Type': 'application/json',
|
|
});
|
|
const jsonResString = getLocalizedContentResponse(language, filterObj);
|
|
res.status(200).send(jsonResString);
|
|
}
|
|
}
|