Ghost/core/frontend/services/theme-engine/middleware.js

210 lines
6.7 KiB
JavaScript
Raw Normal View History

const _ = require('lodash');
const hbs = require('./engine');
const urlUtils = require('../../../shared/url-utils');
const {api} = require('../proxy');
Refactored `common` lib import to use destructuring (#11835) * refactored `core/frontend/apps` to destructure common imports * refactored `core/frontend/services/{apps, redirects, routing}` to destructure common imports * refactored `core/frontend/services/settings` to destructure common imports * refactored remaining `core/frontend/services` to destructure common imports * refactored `core/server/adapters` to destructure common imports * refactored `core/server/data/{db, exporter, schema, validation}` to destructure common imports * refactored `core/server/data/importer` to destructure common imports * refactored `core/server/models/{base, plugins, relations}` to destructure common imports * refactored remaining `core/server/models` to destructure common imports * refactored `core/server/api/canary/utils/serializers/output` to destructure common imports * refactored remaining `core/server/api/canary/utils` to destructure common imports * refactored remaining `core/server/api/canary` to destructure common imports * refactored `core/server/api/shared` to destructure common imports * refactored `core/server/api/v2/utils` to destructure common imports * refactored remaining `core/server/api/v2` to destructure common imports * refactored `core/frontend/meta` to destructure common imports * fixed some tests referencing `common.errors` instead of `@tryghost/errors` - Not all of them need to be updated; only updating the ones that are causing failures * fixed errors import being shadowed by local scope
2020-05-22 21:22:20 +03:00
const errors = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');
const settingsCache = require('../../../shared/settings-cache');
const customThemeSettingsCache = require('../../../shared/custom-theme-settings-cache');
const labs = require('../../../shared/labs');
const activeTheme = require('./active');
const preview = require('./preview');
const messages = {
missingTheme: 'The currently active theme "{theme}" is missing.'
};
// ### Ensure Active Theme
// Ensure there's a properly set & mounted active theme before attempting to serve a site request
// If there is no active theme, throw an error
// Else, ensure the active theme is mounted
function ensureActiveTheme(req, res, next) {
// CASE: this means that the theme hasn't been loaded yet i.e. there is no active theme
if (!activeTheme.get()) {
// This is the one place we ACTUALLY throw an error for a missing theme as it's a request we cannot serve
Refactored `common` lib import to use destructuring (#11835) * refactored `core/frontend/apps` to destructure common imports * refactored `core/frontend/services/{apps, redirects, routing}` to destructure common imports * refactored `core/frontend/services/settings` to destructure common imports * refactored remaining `core/frontend/services` to destructure common imports * refactored `core/server/adapters` to destructure common imports * refactored `core/server/data/{db, exporter, schema, validation}` to destructure common imports * refactored `core/server/data/importer` to destructure common imports * refactored `core/server/models/{base, plugins, relations}` to destructure common imports * refactored remaining `core/server/models` to destructure common imports * refactored `core/server/api/canary/utils/serializers/output` to destructure common imports * refactored remaining `core/server/api/canary/utils` to destructure common imports * refactored remaining `core/server/api/canary` to destructure common imports * refactored `core/server/api/shared` to destructure common imports * refactored `core/server/api/v2/utils` to destructure common imports * refactored remaining `core/server/api/v2` to destructure common imports * refactored `core/frontend/meta` to destructure common imports * fixed some tests referencing `common.errors` instead of `@tryghost/errors` - Not all of them need to be updated; only updating the ones that are causing failures * fixed errors import being shadowed by local scope
2020-05-22 21:22:20 +03:00
return next(new errors.InternalServerError({
// We use the settingsCache here, because the setting will be set,
// even if the theme itself is not usable because it is invalid or missing.
message: tpl(messages.missingTheme, {theme: settingsCache.get('active_theme')})
}));
}
// If the active theme has not yet been mounted, mount it into express
if (!activeTheme.get().mounted) {
activeTheme.get().mount(req.app);
}
next();
}
function calculateLegacyPriceData(products) {
const defaultPrice = {
amount: 0,
currency: 'usd',
interval: 'year',
nickname: ''
};
function makePriceObject(price) {
const numberAmount = 0 + price.amount;
const dollarAmount = numberAmount ? Math.round(numberAmount / 100) : 0;
return {
valueOf() {
return dollarAmount;
},
amount: numberAmount,
currency: price.currency,
nickname: price.name,
interval: price.interval
};
}
const defaultProduct = products[0] || {};
const monthlyPrice = makePriceObject(defaultProduct.monthly_price || defaultPrice);
const yearlyPrice = makePriceObject(defaultProduct.yearly_price || defaultPrice);
const priceData = {
monthly: monthlyPrice,
yearly: yearlyPrice,
currency: monthlyPrice ? monthlyPrice.currency : defaultPrice.currency
};
return priceData;
}
async function getProductAndPricesData() {
try {
const page = await api.canary.productsPublic.browse({
include: ['monthly_price', 'yearly_price'],
limit: 'all'
});
return page.products;
} catch (err) {
return [];
}
}
function getSiteData() {
let siteData = settingsCache.getPublic();
// theme-only computed property added to @site
if (settingsCache.get('members_signup_access') === 'none') {
const escapedUrl = encodeURIComponent(urlUtils.urlFor({relativeUrl: '/rss/'}, true));
siteData.signup_url = `https://feedly.com/i/subscription/feed/${escapedUrl}`;
} else {
siteData.signup_url = '#/portal';
}
return siteData;
}
async function updateGlobalTemplateOptions(req, res, next) {
// Static information, same for every request unless the settings change
// @TODO: bind this once and then update based on events?
// @TODO: decouple theme layer from settings cache using the Content API
const siteData = getSiteData();
const labsData = labs.getAll();
const themeData = {
posts_per_page: activeTheme.get().config('posts_per_page'),
image_sizes: activeTheme.get().config('image_sizes')
};
const themeSettingsData = customThemeSettingsCache.getAll();
const productData = await getProductAndPricesData();
const priceData = calculateLegacyPriceData(productData);
let products = null;
let product = null;
if (productData.length === 1) {
product = productData[0];
} else {
products = productData;
}
// @TODO: only do this if something changed?
// @TODO: remove blog in a major where we are happy to break more themes
{
hbs.updateTemplateOptions({
data: {
blog: siteData,
site: siteData,
labs: labsData,
config: themeData,
price: priceData,
product,
products,
custom: themeSettingsData
}
});
}
next();
}
function updateLocalTemplateData(req, res, next) {
// Pass 'secure' flag to the view engine
// so that templates can choose to render https or http 'url', see url utility
res.locals.secure = req.secure;
next();
}
function updateLocalTemplateOptions(req, res, next) {
const localTemplateOptions = hbs.getLocalTemplateOptions(res.locals);
// adjust @site.url for http/https based on the incoming request
const siteData = {
url: urlUtils.urlFor('home', {secure: req.secure, trailingSlash: false}, true)
};
// @TODO: it would be nicer if this was proper middleware somehow...
const previewData = preview.handle(req, Object.keys(customThemeSettingsCache.getAll()));
// strip custom off of preview data so it doesn't get merged into @site
const customThemeSettingsPreviewData = previewData.custom;
delete previewData.custom;
let customData = {};
if (labs.isSet('customThemeSettings')) {
customData = customThemeSettingsPreviewData;
}
// update site data with any preview values from the request
Object.assign(siteData, previewData);
const member = req.member ? {
uuid: req.member.uuid,
email: req.member.email,
name: req.member.name,
firstname: req.member.name && req.member.name.split(' ')[0],
avatar_image: req.member.avatar_image,
subscriptions: req.member.subscriptions && req.member.subscriptions.map((sub) => {
return Object.assign({}, sub, {
default_payment_card_last4: sub.default_payment_card_last4 || '****'
});
}),
paid: req.member.status !== 'free'
} : null;
hbs.updateLocalTemplateOptions(res.locals, _.merge({}, localTemplateOptions, {
data: {
member: member,
site: siteData,
custom: customData,
// @deprecated: a gscan warning for @blog was added before 3.0 which replaced it with @site
blog: siteData
}
}));
next();
}
module.exports = [
ensureActiveTheme,
updateGlobalTemplateOptions,
updateLocalTemplateData,
updateLocalTemplateOptions
];