mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 05:37:34 +03:00
Refactored members config to use DI
no-issue This makes testing it much easier
This commit is contained in:
parent
fc0e97593b
commit
a1f883edbc
@ -6,13 +6,12 @@ const models = require('../../models');
|
||||
const signinEmail = require('./emails/signin');
|
||||
const signupEmail = require('./emails/signup');
|
||||
const subscribeEmail = require('./emails/subscribe');
|
||||
const config = require('./config');
|
||||
|
||||
const ghostMailer = new mail.GhostMailer();
|
||||
|
||||
module.exports = createApiInstance;
|
||||
|
||||
function createApiInstance() {
|
||||
function createApiInstance(config) {
|
||||
const membersApiInstance = MembersApi({
|
||||
tokenConfig: config.getTokenConfig(),
|
||||
auth: {
|
||||
|
@ -1,10 +1,6 @@
|
||||
const {URL} = require('url');
|
||||
const settingsCache = require('../settings/cache');
|
||||
const ghostVersion = require('../../lib/ghost-version');
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const logging = require('../../../shared/logging');
|
||||
const urlUtils = require('../../../shared/url-utils');
|
||||
|
||||
const COMPLIMENTARY_PLAN = {
|
||||
name: 'Complimentary',
|
||||
@ -13,161 +9,170 @@ const COMPLIMENTARY_PLAN = {
|
||||
amount: '0'
|
||||
};
|
||||
|
||||
// NOTE: the function is an exact duplicate of one in GhostMailer should be extracted
|
||||
// into a common lib once it needs to be reused anywhere else again
|
||||
function getDomain() {
|
||||
const domain = urlUtils.urlFor('home', true).match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));
|
||||
return domain && domain[1];
|
||||
}
|
||||
class MembersConfigProvider {
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {{get: (key: string) => any}} options.settingsCache
|
||||
* @param {{get: (key: string) => any}} options.config
|
||||
* @param {any} options.urlUtils
|
||||
* @param {any} options.logging
|
||||
* @param {{original: string}} options.ghostVersion
|
||||
*/
|
||||
constructor(options) {
|
||||
this._settingsCache = options.settingsCache;
|
||||
this._config = options.config;
|
||||
this._urlUtils = options.urlUtils;
|
||||
this._logging = options.logging;
|
||||
this._ghostVersion = options.ghostVersion;
|
||||
}
|
||||
|
||||
function getEmailFromAddress() {
|
||||
const subscriptionSettings = settingsCache.get('members_subscription_settings') || {};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getDomain() {
|
||||
const domain = this._urlUtils.urlFor('home', true).match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));
|
||||
return domain && domain[1];
|
||||
}
|
||||
|
||||
return `${subscriptionSettings.fromAddress || 'noreply'}@${getDomain()}`;
|
||||
}
|
||||
/**
|
||||
*/
|
||||
getEmailFromAddress() {
|
||||
const subscriptionSettings = this._settingsCache.get('members_subscription_settings') || {};
|
||||
|
||||
/** Copied from theme middleware, remove it there after cleanup to keep this in single place */
|
||||
function getPublicPlans() {
|
||||
const CURRENCY_SYMBOLS = {
|
||||
USD: '$',
|
||||
AUD: '$',
|
||||
CAD: '$',
|
||||
GBP: '£',
|
||||
EUR: '€'
|
||||
};
|
||||
const defaultPriceData = {
|
||||
monthly: 0,
|
||||
yearly: 0
|
||||
};
|
||||
return `${subscriptionSettings.fromAddress || 'noreply'}@${this._getDomain()}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const membersSettings = settingsCache.get('members_subscription_settings');
|
||||
const stripeProcessor = membersSettings.paymentProcessors.find(
|
||||
processor => processor.adapter === 'stripe'
|
||||
getPublicPlans() {
|
||||
const CURRENCY_SYMBOLS = {
|
||||
USD: '$',
|
||||
AUD: '$',
|
||||
CAD: '$',
|
||||
GBP: '£',
|
||||
EUR: '€'
|
||||
};
|
||||
const defaultPriceData = {
|
||||
monthly: 0,
|
||||
yearly: 0
|
||||
};
|
||||
|
||||
try {
|
||||
const membersSettings = this._settingsCache.get('members_subscription_settings');
|
||||
const stripeProcessor = membersSettings.paymentProcessors.find(
|
||||
processor => processor.adapter === 'stripe'
|
||||
);
|
||||
|
||||
const priceData = stripeProcessor.config.plans.reduce((prices, plan) => {
|
||||
const numberAmount = 0 + plan.amount;
|
||||
const dollarAmount = numberAmount ? Math.round(numberAmount / 100) : 0;
|
||||
return Object.assign(prices, {
|
||||
[plan.name.toLowerCase()]: dollarAmount
|
||||
});
|
||||
}, {});
|
||||
|
||||
priceData.currency = String.prototype.toUpperCase.call(stripeProcessor.config.currency || 'usd');
|
||||
priceData.currency_symbol = CURRENCY_SYMBOLS[priceData.currency];
|
||||
|
||||
if (Number.isInteger(priceData.monthly) && Number.isInteger(priceData.yearly)) {
|
||||
return priceData;
|
||||
}
|
||||
|
||||
return defaultPriceData;
|
||||
} catch (err) {
|
||||
return defaultPriceData;
|
||||
}
|
||||
}
|
||||
|
||||
getStripePaymentConfig() {
|
||||
const subscriptionSettings = this._settingsCache.get('members_subscription_settings');
|
||||
|
||||
const stripePaymentProcessor = subscriptionSettings.paymentProcessors.find(
|
||||
paymentProcessor => paymentProcessor.adapter === 'stripe'
|
||||
);
|
||||
|
||||
const priceData = stripeProcessor.config.plans.reduce((prices, plan) => {
|
||||
const numberAmount = 0 + plan.amount;
|
||||
const dollarAmount = numberAmount ? Math.round(numberAmount / 100) : 0;
|
||||
return Object.assign(prices, {
|
||||
[plan.name.toLowerCase()]: dollarAmount
|
||||
});
|
||||
}, {});
|
||||
|
||||
priceData.currency = String.prototype.toUpperCase.call(stripeProcessor.config.currency || 'usd');
|
||||
priceData.currency_symbol = CURRENCY_SYMBOLS[priceData.currency];
|
||||
|
||||
if (Number.isInteger(priceData.monthly) && Number.isInteger(priceData.yearly)) {
|
||||
return priceData;
|
||||
if (!stripePaymentProcessor || !stripePaymentProcessor.config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return defaultPriceData;
|
||||
} catch (err) {
|
||||
return defaultPriceData;
|
||||
}
|
||||
}
|
||||
|
||||
const getApiUrl = ({version, type}) => {
|
||||
const {href} = new URL(
|
||||
urlUtils.getApiPath({version, type}),
|
||||
urlUtils.urlFor('admin', true)
|
||||
);
|
||||
return href;
|
||||
};
|
||||
|
||||
const siteUrl = urlUtils.getSiteUrl();
|
||||
const membersApiUrl = getApiUrl({version: 'v3', type: 'members'});
|
||||
|
||||
function getStripePaymentConfig() {
|
||||
const subscriptionSettings = settingsCache.get('members_subscription_settings');
|
||||
|
||||
const stripePaymentProcessor = subscriptionSettings.paymentProcessors.find(
|
||||
paymentProcessor => paymentProcessor.adapter === 'stripe'
|
||||
);
|
||||
|
||||
if (!stripePaymentProcessor || !stripePaymentProcessor.config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!stripePaymentProcessor.config.public_token || !stripePaymentProcessor.config.secret_token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// NOTE: "Complimentary" plan has to be first in the queue so it is created even if regular plans are not configured
|
||||
stripePaymentProcessor.config.plans.unshift(COMPLIMENTARY_PLAN);
|
||||
|
||||
const webhookHandlerUrl = new URL('/members/webhooks/stripe', siteUrl);
|
||||
|
||||
const checkoutSuccessUrl = new URL(siteUrl);
|
||||
checkoutSuccessUrl.searchParams.set('stripe', 'success');
|
||||
const checkoutCancelUrl = new URL(siteUrl);
|
||||
checkoutCancelUrl.searchParams.set('stripe', 'cancel');
|
||||
|
||||
const billingSuccessUrl = new URL(siteUrl);
|
||||
billingSuccessUrl.searchParams.set('stripe', 'billing-update-success');
|
||||
const billingCancelUrl = new URL(siteUrl);
|
||||
billingCancelUrl.searchParams.set('stripe', 'billing-update-cancel');
|
||||
|
||||
return {
|
||||
publicKey: stripePaymentProcessor.config.public_token,
|
||||
secretKey: stripePaymentProcessor.config.secret_token,
|
||||
checkoutSuccessUrl: checkoutSuccessUrl.href,
|
||||
checkoutCancelUrl: checkoutCancelUrl.href,
|
||||
billingSuccessUrl: billingSuccessUrl.href,
|
||||
billingCancelUrl: billingCancelUrl.href,
|
||||
webhookHandlerUrl: webhookHandlerUrl.href,
|
||||
product: stripePaymentProcessor.config.product,
|
||||
plans: stripePaymentProcessor.config.plans,
|
||||
appInfo: {
|
||||
name: 'Ghost',
|
||||
partner_id: 'pp_partner_DKmRVtTs4j9pwZ',
|
||||
version: ghostVersion.original,
|
||||
url: 'https://ghost.org/'
|
||||
if (!stripePaymentProcessor.config.public_token || !stripePaymentProcessor.config.secret_token) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getAuthSecret() {
|
||||
const hexSecret = settingsCache.get('members_email_auth_secret');
|
||||
if (!hexSecret) {
|
||||
logging.warn('Could not find members_email_auth_secret, using dynamically generated secret');
|
||||
return crypto.randomBytes(64);
|
||||
// NOTE: "Complimentary" plan has to be first in the queue so it is created even if regular plans are not configured
|
||||
stripePaymentProcessor.config.plans.unshift(COMPLIMENTARY_PLAN);
|
||||
|
||||
const siteUrl = this._urlUtils.getSiteUrl();
|
||||
|
||||
const webhookHandlerUrl = new URL('/members/webhooks/stripe', siteUrl);
|
||||
|
||||
const checkoutSuccessUrl = new URL(siteUrl);
|
||||
checkoutSuccessUrl.searchParams.set('stripe', 'success');
|
||||
const checkoutCancelUrl = new URL(siteUrl);
|
||||
checkoutCancelUrl.searchParams.set('stripe', 'cancel');
|
||||
|
||||
const billingSuccessUrl = new URL(siteUrl);
|
||||
billingSuccessUrl.searchParams.set('stripe', 'billing-update-success');
|
||||
const billingCancelUrl = new URL(siteUrl);
|
||||
billingCancelUrl.searchParams.set('stripe', 'billing-update-cancel');
|
||||
|
||||
return {
|
||||
publicKey: stripePaymentProcessor.config.public_token,
|
||||
secretKey: stripePaymentProcessor.config.secret_token,
|
||||
checkoutSuccessUrl: checkoutSuccessUrl.href,
|
||||
checkoutCancelUrl: checkoutCancelUrl.href,
|
||||
billingSuccessUrl: billingSuccessUrl.href,
|
||||
billingCancelUrl: billingCancelUrl.href,
|
||||
webhookHandlerUrl: webhookHandlerUrl.href,
|
||||
product: stripePaymentProcessor.config.product,
|
||||
plans: stripePaymentProcessor.config.plans,
|
||||
appInfo: {
|
||||
name: 'Ghost',
|
||||
partner_id: 'pp_partner_DKmRVtTs4j9pwZ',
|
||||
version: this._ghostVersion.original,
|
||||
url: 'https://ghost.org/'
|
||||
}
|
||||
};
|
||||
}
|
||||
const secret = Buffer.from(hexSecret, 'hex');
|
||||
if (secret.length < 64) {
|
||||
logging.warn('members_email_auth_secret not large enough (64 bytes), using dynamically generated secret');
|
||||
return crypto.randomBytes(64);
|
||||
|
||||
getAuthSecret() {
|
||||
const hexSecret = this._settingsCache.get('members_email_auth_secret');
|
||||
if (!hexSecret) {
|
||||
this._logging.warn('Could not find members_email_auth_secret, using dynamically generated secret');
|
||||
return crypto.randomBytes(64);
|
||||
}
|
||||
const secret = Buffer.from(hexSecret, 'hex');
|
||||
if (secret.length < 64) {
|
||||
this._logging.warn('members_email_auth_secret not large enough (64 bytes), using dynamically generated secret');
|
||||
return crypto.randomBytes(64);
|
||||
}
|
||||
return secret;
|
||||
}
|
||||
|
||||
getAllowSelfSignup() {
|
||||
const subscriptionSettings = this._settingsCache.get('members_subscription_settings');
|
||||
return subscriptionSettings.allowSelfSignup;
|
||||
}
|
||||
|
||||
getTokenConfig() {
|
||||
const {href: membersApiUrl} = new URL(
|
||||
this._urlUtils.getApiPath({version: 'v3', type: 'members'}),
|
||||
this._urlUtils.urlFor('admin', true)
|
||||
);
|
||||
|
||||
return {
|
||||
issuer: membersApiUrl,
|
||||
publicKey: this._settingsCache.get('members_public_key'),
|
||||
privateKey: this._settingsCache.get('members_private_key')
|
||||
};
|
||||
}
|
||||
|
||||
getSigninURL(token, type) {
|
||||
const siteUrl = this._urlUtils.getSiteUrl();
|
||||
const signinURL = new URL(siteUrl);
|
||||
signinURL.pathname = path.join(signinURL.pathname, '/members/');
|
||||
signinURL.searchParams.set('token', token);
|
||||
signinURL.searchParams.set('action', type);
|
||||
return signinURL.href;
|
||||
}
|
||||
return secret;
|
||||
}
|
||||
|
||||
function getAllowSelfSignup() {
|
||||
const subscriptionSettings = settingsCache.get('members_subscription_settings');
|
||||
return subscriptionSettings.allowSelfSignup;
|
||||
}
|
||||
|
||||
function getTokenConfig() {
|
||||
return {
|
||||
issuer: membersApiUrl,
|
||||
publicKey: settingsCache.get('members_public_key'),
|
||||
privateKey: settingsCache.get('members_private_key')
|
||||
};
|
||||
}
|
||||
|
||||
function getSigninURL(token, type) {
|
||||
const signinURL = new URL(siteUrl);
|
||||
signinURL.pathname = path.join(signinURL.pathname, '/members/');
|
||||
signinURL.searchParams.set('token', token);
|
||||
signinURL.searchParams.set('action', type);
|
||||
return signinURL.href;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getEmailFromAddress,
|
||||
getPublicPlans,
|
||||
getStripePaymentConfig,
|
||||
getAllowSelfSignup,
|
||||
getAuthSecret,
|
||||
getTokenConfig,
|
||||
getSigninURL
|
||||
};
|
||||
module.exports = MembersConfigProvider;
|
||||
|
@ -1,10 +1,21 @@
|
||||
const MembersSSR = require('@tryghost/members-ssr');
|
||||
|
||||
const MembersConfigProvider = require('./config');
|
||||
const createMembersApiInstance = require('./api');
|
||||
const {events} = require('../../lib/common');
|
||||
const logging = require('../../../shared/logging');
|
||||
const urlUtils = require('../../../shared/url-utils');
|
||||
const settingsCache = require('../settings/cache');
|
||||
const config = require('../../../shared/config');
|
||||
const ghostVersion = require('../../lib/ghost-version');
|
||||
|
||||
const membersConfig = new MembersConfigProvider({
|
||||
config,
|
||||
settingsCache,
|
||||
urlUtils,
|
||||
logging,
|
||||
ghostVersion
|
||||
});
|
||||
|
||||
let membersApi;
|
||||
|
||||
@ -14,7 +25,7 @@ events.on('settings.edited', function updateSettingFromModel(settingModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reconfiguredMembersAPI = createMembersApiInstance();
|
||||
const reconfiguredMembersAPI = createMembersApiInstance(membersConfig);
|
||||
reconfiguredMembersAPI.bus.on('ready', function () {
|
||||
membersApi = reconfiguredMembersAPI;
|
||||
});
|
||||
@ -26,11 +37,11 @@ events.on('settings.edited', function updateSettingFromModel(settingModel) {
|
||||
const membersService = {
|
||||
contentGating: require('./content-gating'),
|
||||
|
||||
config: require('./config'),
|
||||
config: membersConfig,
|
||||
|
||||
get api() {
|
||||
if (!membersApi) {
|
||||
membersApi = createMembersApiInstance();
|
||||
membersApi = createMembersApiInstance(membersConfig);
|
||||
|
||||
membersApi.bus.on('error', function (err) {
|
||||
logging.error(err);
|
||||
|
Loading…
Reference in New Issue
Block a user