mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-23 10:53:34 +03:00
36803a4290
closes https://github.com/TryGhost/Ghost/issues/12986
refs 1345268089
As part of changes in 4.6, the default price ids for monthly/yearly prices are stored in new settings - `members_monthly_price_id`, `members_yearly_price_id` - which are used to determine current active prices for the site from list of all existing prices. While the last commit updated the prices to use the settings, the data for currency was still used from non-zero prices instead of the new settings value.
- Updated tests to check price currency
207 lines
7.0 KiB
JavaScript
207 lines
7.0 KiB
JavaScript
const _ = require('lodash');
|
|
const hbs = require('./engine');
|
|
const urlUtils = require('../../../shared/url-utils');
|
|
const config = require('../../../shared/config');
|
|
const {i18n, api} = require('../proxy');
|
|
const errors = require('@tryghost/errors');
|
|
const settingsCache = require('../../../server/services/settings/cache');
|
|
const labs = require('../../../server/services/labs');
|
|
const activeTheme = require('./active');
|
|
const preview = require('./preview');
|
|
|
|
// ### 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
|
|
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: i18n.t('errors.middleware.themehandler.missingTheme', {theme: settingsCache.get('active_theme')})
|
|
}));
|
|
}
|
|
|
|
// CASE: bootstrap theme validation failed, we would like to show the errors on the site [only production]
|
|
if (activeTheme.get().error && config.get('env') === 'production') {
|
|
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: i18n.t('errors.middleware.themehandler.invalidTheme', {theme: settingsCache.get('active_theme')}),
|
|
errorDetails: activeTheme.get().error.errorDetails
|
|
}));
|
|
}
|
|
|
|
// If the active theme has not yet been mounted, mount it into express
|
|
if (!activeTheme.get().mounted) {
|
|
activeTheme.get().mount(req.app);
|
|
}
|
|
|
|
next();
|
|
}
|
|
|
|
/*
|
|
* @TODO
|
|
* This should be definitely refactored and we need to consider _some_
|
|
* members settings as publicly readable
|
|
*/
|
|
async function haxGetMembersPriceData() {
|
|
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
|
|
};
|
|
}
|
|
|
|
try {
|
|
const {products} = await api.canary.products.browse({
|
|
include: 'stripe_prices'
|
|
});
|
|
|
|
const defaultProduct = products[0];
|
|
|
|
const monthlyPriceId = settingsCache.get('members_monthly_price_id');
|
|
const yearlyPriceId = settingsCache.get('members_yearly_price_id');
|
|
|
|
const activePrices = defaultProduct.stripe_prices.filter((price) => {
|
|
return price.active;
|
|
});
|
|
|
|
const nonZeroPrices = activePrices.filter((price) => {
|
|
return price.amount !== 0;
|
|
});
|
|
|
|
const monthlyPrice = nonZeroPrices.find((price) => {
|
|
return price.id === monthlyPriceId;
|
|
});
|
|
|
|
const yearlyPrice = nonZeroPrices.find((price) => {
|
|
return price.id === yearlyPriceId;
|
|
});
|
|
|
|
const priceData = {
|
|
monthly: makePriceObject(monthlyPrice || defaultPrice),
|
|
yearly: makePriceObject(yearlyPrice || defaultPrice),
|
|
currency: monthlyPrice ? monthlyPrice.currency : defaultPrice.currency
|
|
};
|
|
|
|
return priceData;
|
|
} catch (err) {
|
|
return {
|
|
monthly: makePriceObject(defaultPrice),
|
|
yearly: makePriceObject(defaultPrice),
|
|
currency: 'usd'
|
|
};
|
|
}
|
|
}
|
|
|
|
function getSiteData(req) {
|
|
let siteData = settingsCache.getPublic();
|
|
|
|
// @TODO: it would be nicer if this was proper middleware somehow...
|
|
siteData = preview.handle(req, siteData);
|
|
|
|
// 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(req);
|
|
const labsData = labs.getAll();
|
|
|
|
const themeData = {
|
|
posts_per_page: activeTheme.get().config('posts_per_page'),
|
|
image_sizes: activeTheme.get().config('image_sizes')
|
|
};
|
|
const priceData = await haxGetMembersPriceData();
|
|
|
|
// @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
|
|
}
|
|
});
|
|
}
|
|
|
|
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);
|
|
const siteData = {
|
|
url: urlUtils.urlFor('home', {secure: req.secure, trailingSlash: false}, true)
|
|
};
|
|
|
|
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,
|
|
// @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
|
|
];
|