ghost 5.62.0 → 5.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/tryghost-adapter-cache-memory-ttl-5.64.0.tgz +0 -0
- package/components/{tryghost-adapter-cache-redis-5.62.0.tgz → tryghost-adapter-cache-redis-5.64.0.tgz} +0 -0
- package/components/{tryghost-adapter-manager-5.62.0.tgz → tryghost-adapter-manager-5.64.0.tgz} +0 -0
- package/components/tryghost-announcement-bar-settings-5.64.0.tgz +0 -0
- package/components/{tryghost-api-framework-5.62.0.tgz → tryghost-api-framework-5.64.0.tgz} +0 -0
- package/components/tryghost-api-version-compatibility-service-5.64.0.tgz +0 -0
- package/components/{tryghost-audience-feedback-5.62.0.tgz → tryghost-audience-feedback-5.64.0.tgz} +0 -0
- package/components/tryghost-bookshelf-repository-5.64.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.64.0.tgz +0 -0
- package/components/tryghost-collections-5.64.0.tgz +0 -0
- package/components/{tryghost-constants-5.62.0.tgz → tryghost-constants-5.64.0.tgz} +0 -0
- package/components/tryghost-custom-theme-settings-service-5.64.0.tgz +0 -0
- package/components/{tryghost-data-generator-5.62.0.tgz → tryghost-data-generator-5.64.0.tgz} +0 -0
- package/components/{tryghost-domain-events-5.62.0.tgz → tryghost-domain-events-5.64.0.tgz} +0 -0
- package/components/{tryghost-donations-5.62.0.tgz → tryghost-donations-5.64.0.tgz} +0 -0
- package/components/tryghost-dynamic-routing-events-5.64.0.tgz +0 -0
- package/components/{tryghost-email-analytics-provider-mailgun-5.62.0.tgz → tryghost-email-analytics-provider-mailgun-5.64.0.tgz} +0 -0
- package/components/tryghost-email-analytics-service-5.64.0.tgz +0 -0
- package/components/{tryghost-email-content-generator-5.62.0.tgz → tryghost-email-content-generator-5.64.0.tgz} +0 -0
- package/components/tryghost-email-events-5.64.0.tgz +0 -0
- package/components/{tryghost-email-service-5.62.0.tgz → tryghost-email-service-5.64.0.tgz} +0 -0
- package/components/tryghost-email-suppression-list-5.64.0.tgz +0 -0
- package/components/tryghost-event-aware-cache-wrapper-5.64.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.64.0.tgz +0 -0
- package/components/{tryghost-external-media-inliner-5.62.0.tgz → tryghost-external-media-inliner-5.64.0.tgz} +0 -0
- package/components/{tryghost-extract-api-key-5.62.0.tgz → tryghost-extract-api-key-5.64.0.tgz} +0 -0
- package/components/{tryghost-html-to-plaintext-5.62.0.tgz → tryghost-html-to-plaintext-5.64.0.tgz} +0 -0
- package/components/tryghost-i18n-5.64.0.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.64.0.tgz +0 -0
- package/components/{tryghost-importer-revue-5.62.0.tgz → tryghost-importer-revue-5.64.0.tgz} +0 -0
- package/components/tryghost-in-memory-repository-5.64.0.tgz +0 -0
- package/components/{tryghost-job-manager-5.62.0.tgz → tryghost-job-manager-5.64.0.tgz} +0 -0
- package/components/{tryghost-link-redirects-5.62.0.tgz → tryghost-link-redirects-5.64.0.tgz} +0 -0
- package/components/tryghost-link-replacer-5.64.0.tgz +0 -0
- package/components/{tryghost-link-tracking-5.62.0.tgz → tryghost-link-tracking-5.64.0.tgz} +0 -0
- package/components/tryghost-magic-link-5.64.0.tgz +0 -0
- package/components/{tryghost-mail-events-5.62.0.tgz → tryghost-mail-events-5.64.0.tgz} +0 -0
- package/components/{tryghost-mailgun-client-5.62.0.tgz → tryghost-mailgun-client-5.64.0.tgz} +0 -0
- package/components/{tryghost-member-attribution-5.62.0.tgz → tryghost-member-attribution-5.64.0.tgz} +0 -0
- package/components/{tryghost-member-events-5.62.0.tgz → tryghost-member-events-5.64.0.tgz} +0 -0
- package/components/tryghost-members-api-5.64.0.tgz +0 -0
- package/components/{tryghost-members-csv-5.62.0.tgz → tryghost-members-csv-5.64.0.tgz} +0 -0
- package/components/{tryghost-members-events-service-5.62.0.tgz → tryghost-members-events-service-5.64.0.tgz} +0 -0
- package/components/{tryghost-members-importer-5.62.0.tgz → tryghost-members-importer-5.64.0.tgz} +0 -0
- package/components/{tryghost-members-offers-5.62.0.tgz → tryghost-members-offers-5.64.0.tgz} +0 -0
- package/components/tryghost-members-payments-5.64.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.64.0.tgz +0 -0
- package/components/{tryghost-members-stripe-service-5.62.0.tgz → tryghost-members-stripe-service-5.64.0.tgz} +0 -0
- package/components/{tryghost-mentions-email-report-5.62.0.tgz → tryghost-mentions-email-report-5.64.0.tgz} +0 -0
- package/components/{tryghost-milestones-5.62.0.tgz → tryghost-milestones-5.64.0.tgz} +0 -0
- package/components/{tryghost-minifier-5.62.0.tgz → tryghost-minifier-5.64.0.tgz} +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.64.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.64.0.tgz +0 -0
- package/components/{tryghost-mw-cache-control-5.62.0.tgz → tryghost-mw-cache-control-5.64.0.tgz} +0 -0
- package/components/{tryghost-mw-error-handler-5.62.0.tgz → tryghost-mw-error-handler-5.64.0.tgz} +0 -0
- package/components/{tryghost-mw-session-from-token-5.62.0.tgz → tryghost-mw-session-from-token-5.64.0.tgz} +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.64.0.tgz +0 -0
- package/components/tryghost-mw-version-match-5.64.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.64.0.tgz +0 -0
- package/components/{tryghost-nql-filter-expansions-5.62.0.tgz → tryghost-nql-filter-expansions-5.64.0.tgz} +0 -0
- package/components/{tryghost-oembed-service-5.62.0.tgz → tryghost-oembed-service-5.64.0.tgz} +0 -0
- package/components/{tryghost-package-json-5.62.0.tgz → tryghost-package-json-5.64.0.tgz} +0 -0
- package/components/tryghost-post-events-5.64.0.tgz +0 -0
- package/components/tryghost-post-revisions-5.64.0.tgz +0 -0
- package/components/tryghost-posts-service-5.64.0.tgz +0 -0
- package/components/tryghost-recommendations-5.64.0.tgz +0 -0
- package/components/tryghost-referrers-5.64.0.tgz +0 -0
- package/components/{tryghost-security-5.62.0.tgz → tryghost-security-5.64.0.tgz} +0 -0
- package/components/tryghost-session-service-5.64.0.tgz +0 -0
- package/components/{tryghost-settings-path-manager-5.62.0.tgz → tryghost-settings-path-manager-5.64.0.tgz} +0 -0
- package/components/tryghost-slack-notifications-5.64.0.tgz +0 -0
- package/components/{tryghost-staff-service-5.62.0.tgz → tryghost-staff-service-5.64.0.tgz} +0 -0
- package/components/tryghost-stats-service-5.64.0.tgz +0 -0
- package/components/{tryghost-tiers-5.62.0.tgz → tryghost-tiers-5.64.0.tgz} +0 -0
- package/components/{tryghost-update-check-service-5.62.0.tgz → tryghost-update-check-service-5.64.0.tgz} +0 -0
- package/components/{tryghost-verification-trigger-5.62.0.tgz → tryghost-verification-trigger-5.64.0.tgz} +0 -0
- package/components/{tryghost-version-notifications-data-service-5.62.0.tgz → tryghost-version-notifications-data-service-5.64.0.tgz} +0 -0
- package/components/{tryghost-webmentions-5.62.0.tgz → tryghost-webmentions-5.64.0.tgz} +0 -0
- package/core/built/admin/assets/{chunk.143.5b5502a550ce35005d0f.js → chunk.143.ff0abfe4495f055b0f3d.js} +5 -5
- package/core/built/admin/assets/{chunk.178.546664edca0b1b0f2ab2.js → chunk.178.3e470522312d6d4ed2e4.js} +4 -4
- package/core/built/admin/assets/{chunk.853.f743ed975e8838475532.js → chunk.237.9b7032162949850f6c76.js} +690 -678
- package/core/built/admin/assets/{ghost-d804aba7bca07fa75d308ab892c508fc.js → ghost-10ff4bcba99e1759098702a12c531352.js} +62 -57
- package/core/built/admin/assets/{ghost-8a4e981c272f793157133814ca7c7e84.css → ghost-33664cad4cd6664a8b5fa56e62c5005f.css} +1 -1
- package/core/built/admin/assets/ghost-dark-0452daeaee3a9b16dcd954ea60dad518.css +1 -0
- package/core/built/admin/assets/{vendor-b50a3e5c2079b8a35d9122a1a4c34ef6.js → vendor-f8ce8bd43cf5dad6608f828ab48cee9b.js} +2 -2
- package/core/built/admin/index.html +6 -6
- package/core/frontend/helpers/foreach.js +8 -0
- package/core/frontend/helpers/get.js +3 -0
- package/core/frontend/meta/schema.js +1 -1
- package/core/frontend/services/data/checks.js +6 -0
- package/core/frontend/src/cards/css/collection.css +186 -0
- package/core/frontend/src/cards/css/header_v2.css +19 -23
- package/core/frontend/src/cards/css/signup.css +20 -23
- package/core/server/api/endpoints/collections.js +5 -1
- package/core/server/api/endpoints/recommendations-public.js +42 -0
- package/core/server/api/endpoints/recommendations.js +2 -1
- package/core/server/api/endpoints/utils/serializers/output/members.js +22 -5
- package/core/server/data/exporter/table-lists.js +3 -1
- package/core/server/data/migrations/versions/5.63/2023-09-12-11-22-10-add-recommendation-click-events-table.js +8 -0
- package/core/server/data/migrations/versions/5.63/2023-09-12-11-22-11-add-recommendation-subscribe-events-table.js +8 -0
- package/core/server/data/migrations/versions/5.63/2023-09-13-13-03-10-add-ghost-core-content-integration.js +57 -0
- package/core/server/data/migrations/versions/5.63/2023-09-13-13-34-11-add-ghost-core-content-integration-key.js +87 -0
- package/core/server/data/migrations/versions/5.64/2023-09-19-04-25-40-truncate-stale-built-in-collections-posts.js +12 -0
- package/core/server/data/migrations/versions/5.64/2023-09-19-04-34-10-repopulate-built-in-collection-posts.js +69 -0
- package/core/server/data/schema/fixtures/fixtures.json +7 -0
- package/core/server/data/schema/schema.js +12 -0
- package/core/server/lib/lexical.js +20 -1
- package/core/server/models/base/plugins/crud.js +0 -6
- package/core/server/models/base/plugins/sanitize.js +1 -1
- package/core/server/models/post.js +1 -1
- package/core/server/models/recommendation-click-event.js +22 -0
- package/core/server/models/recommendation-subscribe-event.js +22 -0
- package/core/server/services/collections/BookshelfCollectionsRepository.js +115 -9
- package/core/server/services/collections/service.js +8 -8
- package/core/server/services/members/middleware.js +14 -1
- package/core/server/services/members/utils.js +1 -0
- package/core/server/services/recommendations/RecommendationServiceWrapper.js +23 -2
- package/core/server/services/settings/SettingsBREADService.js +5 -0
- package/core/server/web/members/app.js +14 -0
- package/core/shared/config/defaults.json +1 -1
- package/core/shared/settings-cache/public.js +2 -1
- package/package.json +159 -159
- package/yarn.lock +381 -369
- package/components/tryghost-adapter-cache-memory-ttl-5.62.0.tgz +0 -0
- package/components/tryghost-announcement-bar-settings-5.62.0.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.62.0.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.62.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.62.0.tgz +0 -0
- package/components/tryghost-collections-5.62.0.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.62.0.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.62.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.62.0.tgz +0 -0
- package/components/tryghost-email-events-5.62.0.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.62.0.tgz +0 -0
- package/components/tryghost-event-aware-cache-wrapper-5.62.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.62.0.tgz +0 -0
- package/components/tryghost-i18n-5.62.0.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.62.0.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.62.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.62.0.tgz +0 -0
- package/components/tryghost-magic-link-5.62.0.tgz +0 -0
- package/components/tryghost-members-api-5.62.0.tgz +0 -0
- package/components/tryghost-members-payments-5.62.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.62.0.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.62.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.62.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.62.0.tgz +0 -0
- package/components/tryghost-mw-version-match-5.62.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.62.0.tgz +0 -0
- package/components/tryghost-post-events-5.62.0.tgz +0 -0
- package/components/tryghost-post-revisions-5.62.0.tgz +0 -0
- package/components/tryghost-posts-service-5.62.0.tgz +0 -0
- package/components/tryghost-recommendations-5.62.0.tgz +0 -0
- package/components/tryghost-referrers-5.62.0.tgz +0 -0
- package/components/tryghost-session-service-5.62.0.tgz +0 -0
- package/components/tryghost-slack-notifications-5.62.0.tgz +0 -0
- package/components/tryghost-stats-service-5.62.0.tgz +0 -0
- package/core/built/admin/assets/ghost-dark-084169b0e968ef763dfbbf63b253e0c6.css +0 -1
- /package/core/built/admin/assets/{chunk.853.f743ed975e8838475532.js.LICENSE.txt → chunk.237.9b7032162949850f6c76.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// For information on writing migrations, see https://www.notion.so/ghost/Database-migrations-eb5b78c435d741d2b34a582d57c24253
|
|
2
|
+
|
|
3
|
+
const logging = require('@tryghost/logging');
|
|
4
|
+
const {default: ObjectID} = require('bson-objectid');
|
|
5
|
+
const {createTransactionalMigration, meta} = require('../../utils');
|
|
6
|
+
|
|
7
|
+
const coreContentIntegration = {
|
|
8
|
+
slug: 'ghost-core-content',
|
|
9
|
+
name: 'Ghost Core Content API',
|
|
10
|
+
description: 'Internal Content API integration for Admin access',
|
|
11
|
+
type: 'core'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const addIntegration = async (knex, integration) => {
|
|
15
|
+
const message = `Adding "${integration.name}" integration`;
|
|
16
|
+
|
|
17
|
+
const existing = await knex('integrations').select('id').where('slug', integration.slug).first();
|
|
18
|
+
|
|
19
|
+
if (existing?.id) {
|
|
20
|
+
logging.warn(`Skipping ${message} - already exists`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
logging.info(message);
|
|
25
|
+
|
|
26
|
+
const now = knex.raw('CURRENT_TIMESTAMP');
|
|
27
|
+
integration.id = (new ObjectID()).toHexString();
|
|
28
|
+
integration.created_at = now;
|
|
29
|
+
integration.created_by = meta.MIGRATION_USER;
|
|
30
|
+
|
|
31
|
+
await knex('integrations').insert(integration);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const removeIntegration = async (knex, integration) => {
|
|
35
|
+
const message = `Removing ${integration.name} integration`;
|
|
36
|
+
|
|
37
|
+
const existing = await knex('integrations').select('id').where('slug', integration.slug).first();
|
|
38
|
+
|
|
39
|
+
if (!existing?.id) {
|
|
40
|
+
logging.warn(`Skipping ${message} - doesn't exist`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
logging.info(message);
|
|
45
|
+
|
|
46
|
+
await knex('api_keys').where('integration_id', existing.id).del();
|
|
47
|
+
await knex('integrations').where('id', existing.id).del();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
module.exports = createTransactionalMigration(
|
|
51
|
+
async function up(knex) {
|
|
52
|
+
await addIntegration(knex, coreContentIntegration);
|
|
53
|
+
},
|
|
54
|
+
async function down(knex) {
|
|
55
|
+
await removeIntegration(knex, coreContentIntegration);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// For information on writing migrations, see https://www.notion.so/ghost/Database-migrations-eb5b78c435d741d2b34a582d57c24253
|
|
2
|
+
|
|
3
|
+
const {InternalServerError} = require('@tryghost/errors');
|
|
4
|
+
const logging = require('@tryghost/logging');
|
|
5
|
+
const security = require('@tryghost/security');
|
|
6
|
+
const {default: ObjectID} = require('bson-objectid');
|
|
7
|
+
const {createTransactionalMigration, meta} = require('../../utils');
|
|
8
|
+
|
|
9
|
+
const coreContentIntegration = {
|
|
10
|
+
slug: 'ghost-core-content',
|
|
11
|
+
name: 'Ghost Core Content API',
|
|
12
|
+
description: 'Internal Content API integration for Admin access',
|
|
13
|
+
type: 'core'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const addIntegrationContentKey = async (knex, integration) => {
|
|
17
|
+
const message = `Adding "${integration.name}" integration content key`;
|
|
18
|
+
|
|
19
|
+
const existingIntegration = await knex('integrations').select('id').where({
|
|
20
|
+
slug: integration.slug
|
|
21
|
+
}).first();
|
|
22
|
+
|
|
23
|
+
if (!existingIntegration) {
|
|
24
|
+
throw new InternalServerError({
|
|
25
|
+
message: `Could not find "${integration.name}" integration`
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const existing = await knex('api_keys').select('id')
|
|
30
|
+
.where('integration_id', existingIntegration.id)
|
|
31
|
+
.where('type', 'content')
|
|
32
|
+
.first();
|
|
33
|
+
|
|
34
|
+
if (existing?.id) {
|
|
35
|
+
logging.warn(`Skipping ${message} - already exists`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
logging.info(message);
|
|
40
|
+
|
|
41
|
+
await knex('api_keys').insert({
|
|
42
|
+
id: (new ObjectID()).toHexString(),
|
|
43
|
+
type: 'content',
|
|
44
|
+
secret: security.secret.create('content'),
|
|
45
|
+
role_id: null,
|
|
46
|
+
integration_id: existingIntegration.id,
|
|
47
|
+
created_at: knex.raw('current_timestamp'),
|
|
48
|
+
created_by: meta.MIGRATION_USER
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const removeIntegrationContentKey = async (knex, integration) => {
|
|
53
|
+
const message = `Removing "${integration.name}" integration content key`;
|
|
54
|
+
|
|
55
|
+
const existingIntegration = await knex('integrations').select('id').where({
|
|
56
|
+
slug: integration.slug
|
|
57
|
+
}).first();
|
|
58
|
+
|
|
59
|
+
if (!existingIntegration?.id) {
|
|
60
|
+
logging.warn(`Skipping ${message} - integration does not exist`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const existing = await knex('api_keys').select('id').where({
|
|
65
|
+
integration_id: existingIntegration.id,
|
|
66
|
+
type: 'content'
|
|
67
|
+
}).first();
|
|
68
|
+
|
|
69
|
+
if (!existing?.id) {
|
|
70
|
+
logging.warn(`Skipping ${message} - content key does not exist`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
logging.info(message);
|
|
75
|
+
|
|
76
|
+
await knex('api_keys').where('id', existing.id).del();
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
module.exports = createTransactionalMigration(
|
|
80
|
+
async function up(knex) {
|
|
81
|
+
await addIntegrationContentKey(knex, coreContentIntegration);
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
async function down(knex) {
|
|
85
|
+
await removeIntegrationContentKey(knex, coreContentIntegration);
|
|
86
|
+
}
|
|
87
|
+
);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const logging = require('@tryghost/logging');
|
|
2
|
+
const {createNonTransactionalMigration} = require('../../utils');
|
|
3
|
+
|
|
4
|
+
module.exports = createNonTransactionalMigration(
|
|
5
|
+
async function up(knex) {
|
|
6
|
+
logging.info('Clearing collections_posts table');
|
|
7
|
+
await knex('collections_posts').truncate();
|
|
8
|
+
},
|
|
9
|
+
async function down() {
|
|
10
|
+
logging.info('Not doing anything - collections_posts table has been truncated');
|
|
11
|
+
}
|
|
12
|
+
);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const logging = require('@tryghost/logging');
|
|
2
|
+
const {default: ObjectID} = require('bson-objectid');
|
|
3
|
+
const {createTransactionalMigration} = require('../../utils');
|
|
4
|
+
|
|
5
|
+
const insertPostCollections = async (knex, collectionId, postIds) => {
|
|
6
|
+
logging.warn(`Batch inserting ${postIds.length} collection posts for collection ${collectionId}`);
|
|
7
|
+
|
|
8
|
+
const collectionPosts = postIds.map((postId) => {
|
|
9
|
+
return {
|
|
10
|
+
id: (new ObjectID()).toHexString(),
|
|
11
|
+
collection_id: collectionId,
|
|
12
|
+
post_id: postId,
|
|
13
|
+
sort_order: 0
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
await knex.batchInsert('collections_posts', collectionPosts, 1000);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
module.exports = createTransactionalMigration(
|
|
21
|
+
async function up(knex) {
|
|
22
|
+
logging.info('Populating built-in collections');
|
|
23
|
+
|
|
24
|
+
const existingLatestCollection = await knex('collections')
|
|
25
|
+
.where({
|
|
26
|
+
slug: 'latest'
|
|
27
|
+
})
|
|
28
|
+
.first();
|
|
29
|
+
|
|
30
|
+
if (!existingLatestCollection) {
|
|
31
|
+
logging.warn('Latest collection does not exists, skipping');
|
|
32
|
+
} else {
|
|
33
|
+
const latestPostsRows = await knex('posts')
|
|
34
|
+
.select('id')
|
|
35
|
+
.where({
|
|
36
|
+
type: 'post'
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const latestPostsIds = latestPostsRows.map(row => row.id);
|
|
40
|
+
|
|
41
|
+
await insertPostCollections(knex, existingLatestCollection.id, latestPostsIds);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const existingFeaturedCollection = await knex('collections')
|
|
45
|
+
.where({
|
|
46
|
+
slug: 'featured'
|
|
47
|
+
})
|
|
48
|
+
.first();
|
|
49
|
+
|
|
50
|
+
if (!existingFeaturedCollection) {
|
|
51
|
+
logging.warn('Featured collection does not exist, skipping');
|
|
52
|
+
} else {
|
|
53
|
+
const featuredPostsRows = await knex('posts')
|
|
54
|
+
.select('id')
|
|
55
|
+
.where({
|
|
56
|
+
featured: true,
|
|
57
|
+
type: 'post'
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const featuredPostsIds = featuredPostsRows.map(row => row.id);
|
|
61
|
+
|
|
62
|
+
await insertPostCollections(knex, existingFeaturedCollection.id, featuredPostsIds);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
async function down(knex) {
|
|
66
|
+
logging.info('Clearing collections_posts table');
|
|
67
|
+
await knex('collections_posts').truncate();
|
|
68
|
+
}
|
|
69
|
+
);
|
|
@@ -807,6 +807,13 @@
|
|
|
807
807
|
"description": "Internal frontend integration",
|
|
808
808
|
"type": "internal",
|
|
809
809
|
"api_keys": [{"type": "content"}]
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
"slug": "ghost-core-content",
|
|
813
|
+
"name": "Ghost Core Content API",
|
|
814
|
+
"description": "Internal Content API integration for Admin access",
|
|
815
|
+
"type": "core",
|
|
816
|
+
"api_keys": [{"type": "content"}]
|
|
810
817
|
}
|
|
811
818
|
]
|
|
812
819
|
}
|
|
@@ -1079,5 +1079,17 @@ module.exports = {
|
|
|
1079
1079
|
one_click_subscribe: {type: 'boolean', nullable: false, defaultTo: false},
|
|
1080
1080
|
created_at: {type: 'dateTime', nullable: false},
|
|
1081
1081
|
updated_at: {type: 'dateTime', nullable: true}
|
|
1082
|
+
},
|
|
1083
|
+
recommendation_click_events: {
|
|
1084
|
+
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
1085
|
+
recommendation_id: {type: 'string', maxlength: 24, nullable: false, references: 'recommendations.id', unique: false, cascadeDelete: true},
|
|
1086
|
+
member_id: {type: 'string', maxlength: 24, nullable: true, references: 'members.id', unique: false, setNullDelete: true},
|
|
1087
|
+
created_at: {type: 'dateTime', nullable: false}
|
|
1088
|
+
},
|
|
1089
|
+
recommendation_subscribe_events: {
|
|
1090
|
+
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
1091
|
+
recommendation_id: {type: 'string', maxlength: 24, nullable: false, references: 'recommendations.id', unique: false, cascadeDelete: true},
|
|
1092
|
+
member_id: {type: 'string', maxlength: 24, nullable: true, references: 'members.id', unique: false, setNullDelete: true},
|
|
1093
|
+
created_at: {type: 'dateTime', nullable: false}
|
|
1082
1094
|
}
|
|
1083
1095
|
};
|
|
@@ -7,6 +7,7 @@ const storage = require('../adapters/storage');
|
|
|
7
7
|
let nodes;
|
|
8
8
|
let lexicalHtmlRenderer;
|
|
9
9
|
let urlTransformMap;
|
|
10
|
+
let postsService;
|
|
10
11
|
|
|
11
12
|
function populateNodes() {
|
|
12
13
|
const {DEFAULT_NODES} = require('@tryghost/kg-default-nodes');
|
|
@@ -28,6 +29,23 @@ module.exports = {
|
|
|
28
29
|
},
|
|
29
30
|
|
|
30
31
|
async render(lexical, userOptions = {}) {
|
|
32
|
+
if (!postsService) {
|
|
33
|
+
const getPostServiceInstance = require('../services/posts/posts-service');
|
|
34
|
+
postsService = getPostServiceInstance();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const getCollectionPosts = async (collectionSlug, postCount) => {
|
|
38
|
+
const transacting = userOptions.transacting;
|
|
39
|
+
const {data} = await postsService.browsePosts({
|
|
40
|
+
context: {public: true}, // mimic Content API request
|
|
41
|
+
collection: collectionSlug,
|
|
42
|
+
limit: postCount,
|
|
43
|
+
transacting
|
|
44
|
+
});
|
|
45
|
+
let posts = data.map(p => p.toJSON());
|
|
46
|
+
return posts;
|
|
47
|
+
};
|
|
48
|
+
|
|
31
49
|
const options = Object.assign({
|
|
32
50
|
siteUrl: config.get('url'),
|
|
33
51
|
imageOptimization: config.get('imageOptimization'),
|
|
@@ -43,7 +61,8 @@ module.exports = {
|
|
|
43
61
|
createDocument() {
|
|
44
62
|
const {JSDOM} = require('jsdom');
|
|
45
63
|
return (new JSDOM()).window.document;
|
|
46
|
-
}
|
|
64
|
+
},
|
|
65
|
+
getCollectionPosts
|
|
47
66
|
}, userOptions);
|
|
48
67
|
|
|
49
68
|
return await this.lexicalHtmlRenderer.render(lexical, options);
|
|
@@ -44,12 +44,6 @@ module.exports = function (Bookshelf) {
|
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
if (options.page && options.limit) {
|
|
48
|
-
itemCollection
|
|
49
|
-
.query('limit', options.limit)
|
|
50
|
-
.query('offset', options.limit * (options.page - 1));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
47
|
const result = await itemCollection.fetchAll(options);
|
|
54
48
|
if (options.withRelated) {
|
|
55
49
|
_.each(result.models, function each(item) {
|
|
@@ -45,7 +45,7 @@ module.exports = function (Bookshelf) {
|
|
|
45
45
|
case 'findOne':
|
|
46
46
|
return baseOptions.concat(extraOptions, ['columns', 'require', 'mongoTransformer']);
|
|
47
47
|
case 'findAll':
|
|
48
|
-
return baseOptions.concat(extraOptions, ['filter', 'columns', 'mongoTransformer'
|
|
48
|
+
return baseOptions.concat(extraOptions, ['filter', 'columns', 'mongoTransformer']);
|
|
49
49
|
case 'findPage':
|
|
50
50
|
return baseOptions.concat(extraOptions, ['filter', 'order', 'autoOrder', 'page', 'limit', 'columns', 'mongoTransformer']);
|
|
51
51
|
default:
|
|
@@ -700,7 +700,7 @@ Post = ghostBookshelf.Model.extend({
|
|
|
700
700
|
)
|
|
701
701
|
) {
|
|
702
702
|
try {
|
|
703
|
-
this.set('html', await lexicalLib.render(this.get('lexical')));
|
|
703
|
+
this.set('html', await lexicalLib.render(this.get('lexical'), {transacting: options.transacting}));
|
|
704
704
|
} catch (err) {
|
|
705
705
|
throw new errors.ValidationError({
|
|
706
706
|
message: tpl(messages.invalidLexicalStructure),
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const errors = require('@tryghost/errors');
|
|
2
|
+
const ghostBookshelf = require('./base');
|
|
3
|
+
|
|
4
|
+
const RecommendationClickEvent = ghostBookshelf.Model.extend({
|
|
5
|
+
tableName: 'recommendation_click_events'
|
|
6
|
+
}, {
|
|
7
|
+
async edit() {
|
|
8
|
+
throw new errors.IncorrectUsageError({
|
|
9
|
+
message: 'Cannot edit RecommendationClickEvent'
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
async destroy() {
|
|
14
|
+
throw new errors.IncorrectUsageError({
|
|
15
|
+
message: 'Cannot destroy RecommendationClickEvent'
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
RecommendationClickEvent: ghostBookshelf.model('RecommendationClickEvent', RecommendationClickEvent)
|
|
22
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const errors = require('@tryghost/errors');
|
|
2
|
+
const ghostBookshelf = require('./base');
|
|
3
|
+
|
|
4
|
+
const RecommendationSubscribeEvent = ghostBookshelf.Model.extend({
|
|
5
|
+
tableName: 'recommendation_subscribe_events'
|
|
6
|
+
}, {
|
|
7
|
+
async edit() {
|
|
8
|
+
throw new errors.IncorrectUsageError({
|
|
9
|
+
message: 'Cannot edit RecommendationSubscribeEvent'
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
async destroy() {
|
|
14
|
+
throw new errors.IncorrectUsageError({
|
|
15
|
+
message: 'Cannot destroy RecommendationSubscribeEvent'
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
RecommendationSubscribeEvent: ghostBookshelf.model('RecommendationSubscribeEvent', RecommendationSubscribeEvent)
|
|
22
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const logger = require('@tryghost/logging');
|
|
2
2
|
const Collection = require('@tryghost/collections').Collection;
|
|
3
3
|
const sentry = require('../../../shared/sentry');
|
|
4
|
+
const {default: ObjectID} = require('bson-objectid');
|
|
4
5
|
/**
|
|
5
6
|
* @typedef {import('@tryghost/collections/src/CollectionRepository')} CollectionRepository
|
|
6
7
|
*/
|
|
@@ -10,8 +11,10 @@ const sentry = require('../../../shared/sentry');
|
|
|
10
11
|
*/
|
|
11
12
|
module.exports = class BookshelfCollectionsRepository {
|
|
12
13
|
#model;
|
|
13
|
-
|
|
14
|
+
#relationModel;
|
|
15
|
+
constructor(model, relationModel) {
|
|
14
16
|
this.#model = model;
|
|
17
|
+
this.#relationModel = relationModel;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
async createTransaction(cb) {
|
|
@@ -25,12 +28,14 @@ module.exports = class BookshelfCollectionsRepository {
|
|
|
25
28
|
async getById(id, options = {}) {
|
|
26
29
|
const model = await this.#model.findOne({id}, {
|
|
27
30
|
require: false,
|
|
28
|
-
withRelated: ['collectionPosts'],
|
|
29
31
|
transacting: options.transaction
|
|
30
32
|
});
|
|
31
33
|
if (!model) {
|
|
32
34
|
return null;
|
|
33
35
|
}
|
|
36
|
+
|
|
37
|
+
model.collectionPostIds = await this.#fetchCollectionPostIds(model.id, options);
|
|
38
|
+
|
|
34
39
|
return this.#modelToCollection(model);
|
|
35
40
|
}
|
|
36
41
|
|
|
@@ -41,28 +46,63 @@ module.exports = class BookshelfCollectionsRepository {
|
|
|
41
46
|
async getBySlug(slug, options = {}) {
|
|
42
47
|
const model = await this.#model.findOne({slug}, {
|
|
43
48
|
require: false,
|
|
44
|
-
withRelated: ['collectionPosts'],
|
|
45
49
|
transacting: options.transaction
|
|
46
50
|
});
|
|
51
|
+
|
|
47
52
|
if (!model) {
|
|
48
53
|
return null;
|
|
49
54
|
}
|
|
55
|
+
|
|
56
|
+
model.collectionPostIds = await this.#fetchCollectionPostIds(model.id, options);
|
|
57
|
+
|
|
50
58
|
return this.#modelToCollection(model);
|
|
51
59
|
}
|
|
52
60
|
|
|
61
|
+
/**
|
|
62
|
+
* NOTE: we are only fetching post_id column here to save memory on
|
|
63
|
+
* instances with a large amount of posts
|
|
64
|
+
*
|
|
65
|
+
* The method could be further optimized to fetch posts for
|
|
66
|
+
* multiple collections at a time.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} collectionId collection to fetch post ids for
|
|
69
|
+
* @param {Object} options bookshelf options
|
|
70
|
+
*
|
|
71
|
+
* @returns {Promise<Array<{post_id: string}>>}
|
|
72
|
+
*/
|
|
73
|
+
async #fetchCollectionPostIds(collectionId, options = {}) {
|
|
74
|
+
const toSelect = options.columns || ['post_id'];
|
|
75
|
+
|
|
76
|
+
const query = this.#relationModel.query();
|
|
77
|
+
if (options.transaction) {
|
|
78
|
+
query.transacting(options.transaction);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return await query
|
|
82
|
+
.select(toSelect)
|
|
83
|
+
.where('collection_id', collectionId);
|
|
84
|
+
}
|
|
85
|
+
|
|
53
86
|
/**
|
|
54
87
|
* @param {object} [options]
|
|
55
88
|
* @param {string} [options.filter]
|
|
56
89
|
* @param {string} [options.order]
|
|
90
|
+
* @param {string[]} [options.withRelated]
|
|
57
91
|
* @param {import('knex').Transaction} [options.transaction]
|
|
58
92
|
*/
|
|
59
93
|
async getAll(options = {}) {
|
|
60
94
|
const models = await this.#model.findAll({
|
|
61
95
|
...options,
|
|
62
|
-
transacting: options.transaction
|
|
63
|
-
withRelated: ['collectionPosts']
|
|
96
|
+
transacting: options.transaction
|
|
64
97
|
});
|
|
65
98
|
|
|
99
|
+
for (const model of models) {
|
|
100
|
+
// NOTE: Ideally collection posts would be fetching as a part of findAll query.
|
|
101
|
+
// Because bookshelf introduced a massive processing and memory overhead
|
|
102
|
+
// we are fetching collection post ids separately using raw knex query
|
|
103
|
+
model.collectionPostIds = await this.#fetchCollectionPostIds(model.id, options);
|
|
104
|
+
}
|
|
105
|
+
|
|
66
106
|
return (await Promise.all(models.map(model => this.#modelToCollection(model)))).filter(entity => !!entity);
|
|
67
107
|
}
|
|
68
108
|
|
|
@@ -75,6 +115,10 @@ module.exports = class BookshelfCollectionsRepository {
|
|
|
75
115
|
}
|
|
76
116
|
|
|
77
117
|
try {
|
|
118
|
+
// NOTE: collectionPosts are not a part of serialized model
|
|
119
|
+
// and are fetched separately to save memory
|
|
120
|
+
const posts = model.collectionPostIds;
|
|
121
|
+
|
|
78
122
|
return await Collection.create({
|
|
79
123
|
id: json.id,
|
|
80
124
|
slug: json.slug,
|
|
@@ -83,7 +127,7 @@ module.exports = class BookshelfCollectionsRepository {
|
|
|
83
127
|
filter: filter,
|
|
84
128
|
type: json.type,
|
|
85
129
|
featureImage: json.feature_image,
|
|
86
|
-
posts:
|
|
130
|
+
posts: posts.map(collectionPost => collectionPost.post_id),
|
|
87
131
|
createdAt: json.created_at,
|
|
88
132
|
updatedAt: json.updated_at
|
|
89
133
|
});
|
|
@@ -101,10 +145,21 @@ module.exports = class BookshelfCollectionsRepository {
|
|
|
101
145
|
* @returns {Promise<void>}
|
|
102
146
|
*/
|
|
103
147
|
async save(collection, options = {}) {
|
|
148
|
+
if (!options.transaction) {
|
|
149
|
+
return this.createTransaction((transaction) => {
|
|
150
|
+
return this.save(collection, {
|
|
151
|
+
...options,
|
|
152
|
+
transaction
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
104
157
|
if (collection.deleted) {
|
|
105
|
-
await this.#
|
|
158
|
+
await this.#relationModel.query().delete().where('collection_id', collection.id).transacting(options.transaction);
|
|
159
|
+
await this.#model.query().delete().where('id', collection.id).transacting(options.transaction);
|
|
106
160
|
return;
|
|
107
161
|
}
|
|
162
|
+
|
|
108
163
|
const data = {
|
|
109
164
|
id: collection.id,
|
|
110
165
|
slug: collection.slug,
|
|
@@ -113,7 +168,6 @@ module.exports = class BookshelfCollectionsRepository {
|
|
|
113
168
|
filter: collection.filter,
|
|
114
169
|
type: collection.type,
|
|
115
170
|
feature_image: collection.featureImage || null,
|
|
116
|
-
posts: collection.posts.map(postId => ({id: postId})),
|
|
117
171
|
created_at: collection.createdAt,
|
|
118
172
|
updated_at: collection.updatedAt
|
|
119
173
|
};
|
|
@@ -130,11 +184,63 @@ module.exports = class BookshelfCollectionsRepository {
|
|
|
130
184
|
await this.#model.add(data, {
|
|
131
185
|
transacting: options.transaction
|
|
132
186
|
});
|
|
187
|
+
const collectionPostsRelations = collection.posts.map((postId, index) => {
|
|
188
|
+
return {
|
|
189
|
+
id: (new ObjectID).toHexString(),
|
|
190
|
+
sort_order: collection.type === 'manual' ? index : 0,
|
|
191
|
+
collection_id: collection.id,
|
|
192
|
+
post_id: postId
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
if (collectionPostsRelations.length > 0) {
|
|
196
|
+
await this.#relationModel.query().insert(collectionPostsRelations).transacting(options.transaction);
|
|
197
|
+
}
|
|
133
198
|
} else {
|
|
134
|
-
|
|
199
|
+
await this.#model.edit(data, {
|
|
135
200
|
id: data.id,
|
|
136
201
|
transacting: options.transaction
|
|
137
202
|
});
|
|
203
|
+
|
|
204
|
+
const collectionPostsRelations = collection.posts.map((postId, index) => {
|
|
205
|
+
return {
|
|
206
|
+
id: (new ObjectID).toHexString(),
|
|
207
|
+
sort_order: collection.type === 'manual' ? index : 0,
|
|
208
|
+
collection_id: collection.id,
|
|
209
|
+
post_id: postId
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const collectionPostRelationsToDeleteIds = [];
|
|
214
|
+
|
|
215
|
+
if (collection.type === 'manual') {
|
|
216
|
+
await this.#relationModel.query().delete().where('collection_id', collection.id).transacting(options.transaction);
|
|
217
|
+
} else {
|
|
218
|
+
const collectionPostsOptions = {
|
|
219
|
+
transaction: options.transaction,
|
|
220
|
+
columns: ['id', 'post_id']
|
|
221
|
+
};
|
|
222
|
+
const existingRelations = await this.#fetchCollectionPostIds(collection.id, collectionPostsOptions);
|
|
223
|
+
|
|
224
|
+
for (const existingRelation of existingRelations) {
|
|
225
|
+
const found = collectionPostsRelations.find((thing) => {
|
|
226
|
+
return thing.post_id === existingRelation.post_id;
|
|
227
|
+
});
|
|
228
|
+
if (found) {
|
|
229
|
+
found.id = null;
|
|
230
|
+
} else {
|
|
231
|
+
collectionPostRelationsToDeleteIds.push(existingRelation.id);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const missingCollectionPostsRelations = collectionPostsRelations.filter(thing => thing.id !== null);
|
|
237
|
+
|
|
238
|
+
if (missingCollectionPostsRelations.length > 0) {
|
|
239
|
+
await this.#relationModel.query().insert(missingCollectionPostsRelations).transacting(options.transaction);
|
|
240
|
+
}
|
|
241
|
+
if (collectionPostRelationsToDeleteIds.length > 0) {
|
|
242
|
+
await this.#relationModel.query().delete().whereIn('id', collectionPostRelationsToDeleteIds).transacting(options.transaction);
|
|
243
|
+
}
|
|
138
244
|
}
|
|
139
245
|
}
|
|
140
246
|
};
|
|
@@ -12,7 +12,7 @@ class CollectionsServiceWrapper {
|
|
|
12
12
|
const DomainEvents = require('@tryghost/domain-events');
|
|
13
13
|
const postsRepository = require('./PostsRepository').getInstance();
|
|
14
14
|
const models = require('../../models');
|
|
15
|
-
const collectionsRepositoryInMemory = new BookshelfCollectionsRepository(models.Collection);
|
|
15
|
+
const collectionsRepositoryInMemory = new BookshelfCollectionsRepository(models.Collection, models.CollectionPost);
|
|
16
16
|
|
|
17
17
|
const collectionsService = new CollectionsService({
|
|
18
18
|
collectionsRepository: collectionsRepositoryInMemory,
|
|
@@ -33,16 +33,16 @@ class CollectionsServiceWrapper {
|
|
|
33
33
|
async init() {
|
|
34
34
|
const config = require('../../../shared/config');
|
|
35
35
|
const labs = require('../../../shared/labs');
|
|
36
|
+
|
|
36
37
|
// host setting OR labs "collections" flag has to be enabled to run collections service
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
if (config.get('hostSettings:collections:enabled') || labs.isSet('collections')) {
|
|
39
|
+
if (inited) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
inited = true;
|
|
44
|
+
this.api.subscribeToEvents();
|
|
43
45
|
}
|
|
44
|
-
inited = true;
|
|
45
|
-
this.api.subscribeToEvents();
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -5,7 +5,10 @@ const emailSuppressionList = require('../email-suppression-list');
|
|
|
5
5
|
const models = require('../../models');
|
|
6
6
|
const urlUtils = require('../../../shared/url-utils');
|
|
7
7
|
const spamPrevention = require('../../web/shared/middleware/api/spam-prevention');
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
formattedMemberResponse,
|
|
10
|
+
formatNewsletterResponse
|
|
11
|
+
} = require('./utils');
|
|
9
12
|
const errors = require('@tryghost/errors');
|
|
10
13
|
const tpl = require('@tryghost/tpl');
|
|
11
14
|
|
|
@@ -144,6 +147,11 @@ const getMemberNewsletters = async function getMemberNewsletters(req, res) {
|
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
const data = _.pick(memberData.toJSON(), 'uuid', 'email', 'name', 'newsletters', 'enable_comment_notifications', 'status');
|
|
150
|
+
|
|
151
|
+
if (data.newsletters) {
|
|
152
|
+
data.newsletters = formatNewsletterResponse(data.newsletters);
|
|
153
|
+
}
|
|
154
|
+
|
|
147
155
|
return res.json(data);
|
|
148
156
|
} catch (err) {
|
|
149
157
|
res.writeHead(400);
|
|
@@ -175,6 +183,11 @@ const updateMemberNewsletters = async function updateMemberNewsletters(req, res)
|
|
|
175
183
|
|
|
176
184
|
const updatedMember = await membersService.api.members.update(data, options);
|
|
177
185
|
const updatedMemberData = _.pick(updatedMember.toJSON(), ['uuid', 'email', 'name', 'newsletters', 'enable_comment_notifications', 'status']);
|
|
186
|
+
|
|
187
|
+
if (updatedMemberData.newsletters) {
|
|
188
|
+
updatedMemberData.newsletters = formatNewsletterResponse(updatedMemberData.newsletters);
|
|
189
|
+
}
|
|
190
|
+
|
|
178
191
|
res.json(updatedMemberData);
|
|
179
192
|
} catch (err) {
|
|
180
193
|
res.writeHead(400);
|