ghost 5.38.0 → 5.40.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.
Files changed (183) hide show
  1. package/components/{tryghost-adapter-cache-memory-ttl-5.38.0.tgz → tryghost-adapter-cache-memory-ttl-5.40.0.tgz} +0 -0
  2. package/components/tryghost-adapter-cache-redis-5.40.0.tgz +0 -0
  3. package/components/tryghost-adapter-manager-5.40.0.tgz +0 -0
  4. package/components/{tryghost-api-framework-5.38.0.tgz → tryghost-api-framework-5.40.0.tgz} +0 -0
  5. package/components/tryghost-api-version-compatibility-service-5.40.0.tgz +0 -0
  6. package/components/tryghost-audience-feedback-5.40.0.tgz +0 -0
  7. package/components/tryghost-bootstrap-socket-5.40.0.tgz +0 -0
  8. package/components/tryghost-constants-5.40.0.tgz +0 -0
  9. package/components/{tryghost-custom-theme-settings-service-5.38.0.tgz → tryghost-custom-theme-settings-service-5.40.0.tgz} +0 -0
  10. package/components/tryghost-data-generator-5.40.0.tgz +0 -0
  11. package/components/{tryghost-domain-events-5.38.0.tgz → tryghost-domain-events-5.40.0.tgz} +0 -0
  12. package/components/tryghost-dynamic-routing-events-5.40.0.tgz +0 -0
  13. package/components/tryghost-email-analytics-provider-mailgun-5.40.0.tgz +0 -0
  14. package/components/tryghost-email-analytics-service-5.40.0.tgz +0 -0
  15. package/components/tryghost-email-content-generator-5.40.0.tgz +0 -0
  16. package/components/tryghost-email-events-5.40.0.tgz +0 -0
  17. package/components/tryghost-email-service-5.40.0.tgz +0 -0
  18. package/components/{tryghost-email-suppression-list-5.38.0.tgz → tryghost-email-suppression-list-5.40.0.tgz} +0 -0
  19. package/components/tryghost-event-aware-cache-wrapper-5.40.0.tgz +0 -0
  20. package/components/{tryghost-express-dynamic-redirects-5.38.0.tgz → tryghost-express-dynamic-redirects-5.40.0.tgz} +0 -0
  21. package/components/tryghost-external-media-inliner-5.40.0.tgz +0 -0
  22. package/components/tryghost-extract-api-key-5.40.0.tgz +0 -0
  23. package/components/tryghost-html-to-plaintext-5.40.0.tgz +0 -0
  24. package/components/tryghost-i18n-5.40.0.tgz +0 -0
  25. package/components/tryghost-importer-handler-content-files-5.40.0.tgz +0 -0
  26. package/components/tryghost-importer-revue-5.40.0.tgz +0 -0
  27. package/components/tryghost-job-manager-5.40.0.tgz +0 -0
  28. package/components/tryghost-link-redirects-5.40.0.tgz +0 -0
  29. package/components/tryghost-link-replacer-5.40.0.tgz +0 -0
  30. package/components/{tryghost-link-tracking-5.38.0.tgz → tryghost-link-tracking-5.40.0.tgz} +0 -0
  31. package/components/tryghost-magic-link-5.40.0.tgz +0 -0
  32. package/components/tryghost-mailgun-client-5.40.0.tgz +0 -0
  33. package/components/tryghost-member-attribution-5.40.0.tgz +0 -0
  34. package/components/tryghost-member-events-5.40.0.tgz +0 -0
  35. package/components/tryghost-members-api-5.40.0.tgz +0 -0
  36. package/components/tryghost-members-csv-5.40.0.tgz +0 -0
  37. package/components/{tryghost-members-events-service-5.38.0.tgz → tryghost-members-events-service-5.40.0.tgz} +0 -0
  38. package/components/tryghost-members-importer-5.40.0.tgz +0 -0
  39. package/components/tryghost-members-offers-5.40.0.tgz +0 -0
  40. package/components/tryghost-members-payments-5.40.0.tgz +0 -0
  41. package/components/tryghost-members-ssr-5.40.0.tgz +0 -0
  42. package/components/tryghost-members-stripe-service-5.40.0.tgz +0 -0
  43. package/components/tryghost-mentions-email-report-5.40.0.tgz +0 -0
  44. package/components/tryghost-milestones-5.40.0.tgz +0 -0
  45. package/components/{tryghost-minifier-5.38.0.tgz → tryghost-minifier-5.40.0.tgz} +0 -0
  46. package/components/tryghost-mw-api-version-mismatch-5.40.0.tgz +0 -0
  47. package/components/{tryghost-mw-cache-control-5.38.0.tgz → tryghost-mw-cache-control-5.40.0.tgz} +0 -0
  48. package/components/tryghost-mw-error-handler-5.40.0.tgz +0 -0
  49. package/components/tryghost-mw-session-from-token-5.40.0.tgz +0 -0
  50. package/components/tryghost-mw-update-user-last-seen-5.40.0.tgz +0 -0
  51. package/components/{tryghost-mw-version-match-5.38.0.tgz → tryghost-mw-version-match-5.40.0.tgz} +0 -0
  52. package/components/tryghost-mw-vhost-5.40.0.tgz +0 -0
  53. package/components/tryghost-oembed-service-5.40.0.tgz +0 -0
  54. package/components/tryghost-package-json-5.40.0.tgz +0 -0
  55. package/components/tryghost-posts-service-5.40.0.tgz +0 -0
  56. package/components/tryghost-referrers-5.40.0.tgz +0 -0
  57. package/components/tryghost-security-5.40.0.tgz +0 -0
  58. package/components/tryghost-session-service-5.40.0.tgz +0 -0
  59. package/components/tryghost-settings-path-manager-5.40.0.tgz +0 -0
  60. package/components/tryghost-slack-notifications-5.40.0.tgz +0 -0
  61. package/components/tryghost-staff-service-5.40.0.tgz +0 -0
  62. package/components/tryghost-stats-service-5.40.0.tgz +0 -0
  63. package/components/tryghost-tiers-5.40.0.tgz +0 -0
  64. package/components/{tryghost-update-check-service-5.38.0.tgz → tryghost-update-check-service-5.40.0.tgz} +0 -0
  65. package/components/tryghost-verification-trigger-5.40.0.tgz +0 -0
  66. package/components/tryghost-version-notifications-data-service-5.40.0.tgz +0 -0
  67. package/components/tryghost-webmentions-5.40.0.tgz +0 -0
  68. package/core/boot.js +7 -0
  69. package/core/built/admin/assets/{chunk.143.c6802c882a911797ce4f.js → chunk.143.9e105aa0a9236484523a.js} +8 -8
  70. package/core/built/admin/assets/{chunk.178.09faefd4027fcba4113d.js → chunk.178.9b53f20952b9ae4763a2.js} +4 -4
  71. package/core/built/admin/assets/{chunk.220.9ca2950240aba3fced21.js → chunk.462.b66cfed1f66c8d2678b2.js} +9407 -9041
  72. package/core/built/admin/assets/chunk.808.2e76eb12fa4d7be8cb23.js +5 -0
  73. package/core/built/admin/assets/{ghost-35103ff053c43f1dfa7f35821c3c2412.js → ghost-467b96c17c6bc0d06fa88e85074fbecf.js} +299 -275
  74. package/core/built/admin/assets/ghost-68ea49029c6d45b4aa090f4e218917aa.css +1 -0
  75. package/core/built/admin/assets/ghost-dark-c9b38252afc29364507e8c92e4ba9933.css +1 -0
  76. package/core/built/admin/assets/img/latest-posts-1-ea1e669b275f2c7dfa23abd4be8d7ad5.png +0 -0
  77. package/core/built/admin/assets/img/latest-posts-2-e6b0809353ac31642839a6f3bab0d4e8.png +0 -0
  78. package/core/built/admin/assets/img/latest-posts-3-ac8c2e95dd9adecb9029a7b037ed7f15.png +0 -0
  79. package/core/built/admin/assets/{vendor-b982e3bf1020bff77b2a3c44d5f59e55.js → vendor-d08b4de6f990b9cd7b5a92d61952a5bb.js} +54 -49
  80. package/core/built/admin/index.html +6 -6
  81. package/core/frontend/meta/schema.js +4 -16
  82. package/core/server/api/endpoints/db.js +10 -2
  83. package/core/server/api/endpoints/members.js +8 -1
  84. package/core/server/api/endpoints/posts.js +30 -1
  85. package/core/server/api/endpoints/utils/serializers/output/mappers/emails.js +2 -1
  86. package/core/server/api/endpoints/utils/serializers/output/posts.js +5 -0
  87. package/core/server/data/importer/import-manager.js +3 -8
  88. package/core/server/data/migrations/versions/5.39/2023-03-13-09-29-add-newsletter-show-post-title-section.js +7 -0
  89. package/core/server/data/migrations/versions/5.39/2023-03-13-13-11-add-newsletter-show-comment-cta.js +7 -0
  90. package/core/server/data/migrations/versions/5.39/2023-03-13-14-30-add-newsletter-show-subscription-details.js +7 -0
  91. package/core/server/data/migrations/versions/5.39/2023-03-14-12-26-add-last-mentions-email-report-timestamp-setting.js +8 -0
  92. package/core/server/data/migrations/versions/5.40/2023-03-13-14-05-add-newsletter-show-latest-posts.js +7 -0
  93. package/core/server/data/migrations/versions/5.40/2023-03-21-18-42-add-self-serve-integration-role.js +31 -0
  94. package/core/server/data/migrations/versions/5.40/2023-03-21-18-43-add-self-serve-migration-and-permissions.js +25 -0
  95. package/core/server/data/migrations/versions/5.40/2023-03-21-18-52-add-self-serve-integration.js +37 -0
  96. package/core/server/data/migrations/versions/5.40/2023-03-21-19-02-add-self-serve-integration-api-key.js +72 -0
  97. package/core/server/data/schema/default-settings/default-settings.json +4 -0
  98. package/core/server/data/schema/fixtures/fixtures.json +21 -0
  99. package/core/server/data/schema/schema.js +4 -0
  100. package/core/server/lib/request-external.js +8 -4
  101. package/core/server/models/base/plugins/relations.js +22 -0
  102. package/core/server/models/base/plugins/sanitize.js +1 -1
  103. package/core/server/models/newsletter.js +4 -0
  104. package/core/server/services/email-service/wrapper.js +4 -1
  105. package/core/server/services/i18n.js +14 -0
  106. package/core/server/services/member-attribution/index.js +2 -1
  107. package/core/server/services/members/api.js +43 -42
  108. package/core/server/services/members/emails/signin.js +9 -9
  109. package/core/server/services/mentions/BookshelfMentionRepository.js +11 -0
  110. package/core/server/services/mentions/service.js +4 -0
  111. package/core/server/services/mentions-email-report/StartMentionEmailReportJob.js +16 -0
  112. package/core/server/services/mentions-email-report/index.js +1 -0
  113. package/core/server/services/mentions-email-report/job.js +11 -0
  114. package/core/server/services/mentions-email-report/service.js +162 -0
  115. package/core/server/services/milestones/BookshelfMilestoneRepository.js +18 -6
  116. package/core/server/services/milestones/service.js +17 -15
  117. package/core/server/services/posts/posts-service.js +22 -119
  118. package/core/server/services/public-config/config.js +1 -1
  119. package/core/server/services/slack-notifications/service.js +1 -2
  120. package/core/server/services/webhooks/trigger.js +2 -2
  121. package/core/server/web/api/endpoints/admin/routes.js +2 -1
  122. package/core/shared/config/defaults.json +3 -4
  123. package/core/shared/config/overrides.json +3 -1
  124. package/core/shared/labs.js +3 -4
  125. package/package.json +149 -142
  126. package/yarn.lock +3544 -1240
  127. package/components/tryghost-adapter-cache-redis-5.38.0.tgz +0 -0
  128. package/components/tryghost-adapter-manager-5.38.0.tgz +0 -0
  129. package/components/tryghost-api-version-compatibility-service-5.38.0.tgz +0 -0
  130. package/components/tryghost-audience-feedback-5.38.0.tgz +0 -0
  131. package/components/tryghost-bootstrap-socket-5.38.0.tgz +0 -0
  132. package/components/tryghost-constants-5.38.0.tgz +0 -0
  133. package/components/tryghost-data-generator-5.38.0.tgz +0 -0
  134. package/components/tryghost-dynamic-routing-events-5.38.0.tgz +0 -0
  135. package/components/tryghost-email-analytics-provider-mailgun-5.38.0.tgz +0 -0
  136. package/components/tryghost-email-analytics-service-5.38.0.tgz +0 -0
  137. package/components/tryghost-email-content-generator-5.38.0.tgz +0 -0
  138. package/components/tryghost-email-events-5.38.0.tgz +0 -0
  139. package/components/tryghost-email-service-5.38.0.tgz +0 -0
  140. package/components/tryghost-event-aware-cache-wrapper-5.38.0.tgz +0 -0
  141. package/components/tryghost-external-media-inliner-5.38.0.tgz +0 -0
  142. package/components/tryghost-extract-api-key-5.38.0.tgz +0 -0
  143. package/components/tryghost-html-to-plaintext-5.38.0.tgz +0 -0
  144. package/components/tryghost-i18n-5.38.0.tgz +0 -0
  145. package/components/tryghost-importer-handler-content-files-5.38.0.tgz +0 -0
  146. package/components/tryghost-importer-revue-5.38.0.tgz +0 -0
  147. package/components/tryghost-job-manager-5.38.0.tgz +0 -0
  148. package/components/tryghost-link-redirects-5.38.0.tgz +0 -0
  149. package/components/tryghost-link-replacer-5.38.0.tgz +0 -0
  150. package/components/tryghost-magic-link-5.38.0.tgz +0 -0
  151. package/components/tryghost-mailgun-client-5.38.0.tgz +0 -0
  152. package/components/tryghost-member-attribution-5.38.0.tgz +0 -0
  153. package/components/tryghost-member-events-5.38.0.tgz +0 -0
  154. package/components/tryghost-members-api-5.38.0.tgz +0 -0
  155. package/components/tryghost-members-csv-5.38.0.tgz +0 -0
  156. package/components/tryghost-members-importer-5.38.0.tgz +0 -0
  157. package/components/tryghost-members-offers-5.38.0.tgz +0 -0
  158. package/components/tryghost-members-payments-5.38.0.tgz +0 -0
  159. package/components/tryghost-members-ssr-5.38.0.tgz +0 -0
  160. package/components/tryghost-members-stripe-service-5.38.0.tgz +0 -0
  161. package/components/tryghost-milestones-5.38.0.tgz +0 -0
  162. package/components/tryghost-mw-api-version-mismatch-5.38.0.tgz +0 -0
  163. package/components/tryghost-mw-error-handler-5.38.0.tgz +0 -0
  164. package/components/tryghost-mw-session-from-token-5.38.0.tgz +0 -0
  165. package/components/tryghost-mw-update-user-last-seen-5.38.0.tgz +0 -0
  166. package/components/tryghost-mw-vhost-5.38.0.tgz +0 -0
  167. package/components/tryghost-oembed-service-5.38.0.tgz +0 -0
  168. package/components/tryghost-package-json-5.38.0.tgz +0 -0
  169. package/components/tryghost-referrers-5.38.0.tgz +0 -0
  170. package/components/tryghost-security-5.38.0.tgz +0 -0
  171. package/components/tryghost-session-service-5.38.0.tgz +0 -0
  172. package/components/tryghost-settings-path-manager-5.38.0.tgz +0 -0
  173. package/components/tryghost-slack-notifications-5.38.0.tgz +0 -0
  174. package/components/tryghost-staff-service-5.38.0.tgz +0 -0
  175. package/components/tryghost-stats-service-5.38.0.tgz +0 -0
  176. package/components/tryghost-tiers-5.38.0.tgz +0 -0
  177. package/components/tryghost-verification-trigger-5.38.0.tgz +0 -0
  178. package/components/tryghost-version-notifications-data-service-5.38.0.tgz +0 -0
  179. package/components/tryghost-webmentions-5.38.0.tgz +0 -0
  180. package/core/built/admin/assets/chunk.79.acb7dd01e1c785f4920c.js +0 -287
  181. package/core/built/admin/assets/ghost-a9307c9cfe26a4bc621e02cd3bae421a.css +0 -1
  182. package/core/built/admin/assets/ghost-dark-f309cf445255344e4861a95ecb8f1920.css +0 -1
  183. /package/core/built/admin/assets/{chunk.220.9ca2950240aba3fced21.js.LICENSE.txt → chunk.462.b66cfed1f66c8d2678b2.js.LICENSE.txt} +0 -0
@@ -9,7 +9,7 @@ const allowedIncludes = [
9
9
  'email',
10
10
  'tiers',
11
11
  'newsletter',
12
- 'count.conversions',
12
+ 'count.conversions',
13
13
  'count.signups',
14
14
  'count.paid_conversions',
15
15
  'count.clicks',
@@ -57,6 +57,35 @@ module.exports = {
57
57
  }
58
58
  },
59
59
 
60
+ exportCSV: {
61
+ options: [
62
+ 'limit',
63
+ 'filter',
64
+ 'order'
65
+ ],
66
+ headers: {
67
+ disposition: {
68
+ type: 'csv',
69
+ value() {
70
+ const datetime = (new Date()).toJSON().substring(0, 10);
71
+ return `posts.${datetime}.csv`;
72
+ }
73
+ }
74
+ },
75
+ response: {
76
+ format: 'plain'
77
+ },
78
+ permissions: {
79
+ method: 'browse'
80
+ },
81
+ validation: {},
82
+ async query(frame) {
83
+ return {
84
+ data: await postsService.export(frame)
85
+ };
86
+ }
87
+ },
88
+
60
89
  read: {
61
90
  options: [
62
91
  'include',
@@ -7,7 +7,8 @@ module.exports = (model, frame) => {
7
7
 
8
8
  // Ensure we're not outputting unwanted replacement strings when viewing email contents
9
9
  // TODO: extract this to a utility, it's duplicated in the email-preview API controller
10
- if (jsonModel.html) {
10
+ if (jsonModel.html && emailService.renderer && emailService.service) {
11
+ // In worker threads the renderer and servie service are not available, but we don't need to do this, so okay to skip.
11
12
  const replacements = emailService.renderer.buildReplacementDefinitions({html: jsonModel.html, newsletterUuid: 'preview'});
12
13
  const exampleMember = emailService.service.getDefaultExampleMember();
13
14
 
@@ -1,6 +1,7 @@
1
1
  const debug = require('@tryghost/debug')('api:endpoints:utils:serializers:output:posts');
2
2
  const mappers = require('./mappers');
3
3
  const membersService = require('../../../../../services/members');
4
+ const papaparse = require('papaparse');
4
5
 
5
6
  module.exports = {
6
7
  async all(models, apiConfig, frame) {
@@ -32,5 +33,9 @@ module.exports = {
32
33
  frame.response = {
33
34
  posts: [post]
34
35
  };
36
+ },
37
+
38
+ exportCSV(models, apiConfig, frame) {
39
+ frame.response = papaparse.unparse(models.data);
35
40
  }
36
41
  };
@@ -61,6 +61,7 @@ class ImportManager {
61
61
  // in the importer, so we need to keep it as general "content" unless
62
62
  // it becomes a strict requirement
63
63
  directories: ['media', 'content'],
64
+ ignoreRootFolderFiles: true,
64
65
  extensions: config.get('uploads').media.extensions,
65
66
  contentTypes: config.get('uploads').media.contentTypes,
66
67
  contentPath: config.getContentPath('media'),
@@ -74,6 +75,7 @@ class ImportManager {
74
75
  // in the importer, so we need to keep it as general "content" unless
75
76
  // it becomes a strict requirement
76
77
  directories: ['files', 'content'],
78
+ ignoreRootFolderFiles: true,
77
79
  extensions: config.get('uploads').files.extensions,
78
80
  contentTypes: config.get('uploads').files.contentTypes,
79
81
  contentPath: config.getContentPath('files'),
@@ -301,14 +303,7 @@ class ImportManager {
301
303
  const baseDir = this.getBaseDirectory(zipDirectory);
302
304
 
303
305
  for (const handler of this.handlers) {
304
- let files = [];
305
- if (handler.directories?.length > 0) {
306
- for (const dir of handler.directories) {
307
- files.push(...this.getFilesFromZip(handler, path.join(zipDirectory, (baseDir || ''), dir)));
308
- }
309
- } else {
310
- files.push(...this.getFilesFromZip(handler, zipDirectory));
311
- }
306
+ const files = this.getFilesFromZip(handler, zipDirectory);
312
307
 
313
308
  debug('handler', handler.type, files);
314
309
 
@@ -0,0 +1,7 @@
1
+ const {createAddColumnMigration} = require('../../utils');
2
+
3
+ module.exports = createAddColumnMigration('newsletters', 'show_post_title_section', {
4
+ type: 'boolean',
5
+ nullable: false,
6
+ defaultTo: true
7
+ });
@@ -0,0 +1,7 @@
1
+ const {createAddColumnMigration} = require('../../utils');
2
+
3
+ module.exports = createAddColumnMigration('newsletters', 'show_comment_cta', {
4
+ type: 'boolean',
5
+ nullable: false,
6
+ defaultTo: true
7
+ });
@@ -0,0 +1,7 @@
1
+ const {createAddColumnMigration} = require('../../utils');
2
+
3
+ module.exports = createAddColumnMigration('newsletters', 'show_subscription_details', {
4
+ type: 'boolean',
5
+ nullable: false,
6
+ defaultTo: false
7
+ });
@@ -0,0 +1,8 @@
1
+ const {addSetting} = require('../../utils');
2
+
3
+ module.exports = addSetting({
4
+ key: 'last_mentions_report_email_timestamp',
5
+ value: null,
6
+ type: 'number',
7
+ group: 'core'
8
+ });
@@ -0,0 +1,7 @@
1
+ const {createAddColumnMigration} = require('../../utils');
2
+
3
+ module.exports = createAddColumnMigration('newsletters', 'show_latest_posts', {
4
+ type: 'boolean',
5
+ nullable: false,
6
+ defaultTo: false
7
+ });
@@ -0,0 +1,31 @@
1
+ const logging = require('@tryghost/logging');
2
+ const {default: ObjectID} = require('bson-objectid');
3
+ const {createTransactionalMigration, meta} = require('../../utils');
4
+
5
+ module.exports = createTransactionalMigration(
6
+ async function up(knex) {
7
+ logging.info('Creating "Self-Serve Migration Integration" role');
8
+ const existingRole = await knex('roles').where({
9
+ name: 'Self-Serve Migration Integration'
10
+ }).first();
11
+
12
+ if (existingRole) {
13
+ logging.warn('"Self-Serve Migration Integration" role already exists, skipping');
14
+ return;
15
+ }
16
+
17
+ await knex('roles').insert({
18
+ id: (new ObjectID()).toHexString(),
19
+ name: 'Self-Serve Migration Integration',
20
+ description: 'Core Integration for the Ghost Explore directory',
21
+ created_by: meta.MIGRATION_USER,
22
+ created_at: knex.raw('current_timestamp')
23
+ });
24
+ },
25
+ async function down(knex) {
26
+ logging.info('Deleting role "Self-Serve Migration Integration"');
27
+ await knex('roles').where({
28
+ name: 'Self-Serve Migration Integration'
29
+ }).del();
30
+ }
31
+ );
@@ -0,0 +1,25 @@
1
+ const {combineTransactionalMigrations, addPermissionWithRoles} = require('../../utils');
2
+
3
+ module.exports = combineTransactionalMigrations(
4
+ addPermissionWithRoles({
5
+ name: 'Import database',
6
+ action: 'importContent',
7
+ object: 'db'
8
+ }, [
9
+ 'Self-Serve Migration Integration'
10
+ ]),
11
+ addPermissionWithRoles({
12
+ name: 'Add Members',
13
+ action: 'add',
14
+ object: 'member'
15
+ }, [
16
+ 'Self-Serve Migration Integration'
17
+ ]),
18
+ addPermissionWithRoles({
19
+ name: 'Read tags',
20
+ action: 'read',
21
+ object: 'tag'
22
+ }, [
23
+ 'Self-Serve Migration Integration'
24
+ ])
25
+ );
@@ -0,0 +1,37 @@
1
+ const logging = require('@tryghost/logging');
2
+ const {default: ObjectID} = require('bson-objectid');
3
+ const {createTransactionalMigration, meta} = require('../../utils');
4
+
5
+ module.exports = createTransactionalMigration(
6
+ async function up(knex) {
7
+ logging.info('Creating "Self-Serve Migration Integration"');
8
+ const existingIntegration = await knex('integrations').where({
9
+ name: 'Self-Serve Migration Integration',
10
+ slug: 'self-serve-migration'
11
+ }).first();
12
+
13
+ if (existingIntegration) {
14
+ logging.warn('Integration already exists, skipping');
15
+ return;
16
+ }
17
+
18
+ await knex('integrations').insert({
19
+ id: (new ObjectID()).toHexString(),
20
+ type: 'core',
21
+ name: 'Self-Serve Migration Integration',
22
+ description: 'Core Integration for the Self-Serve migration tool',
23
+ slug: 'self-serve-migration',
24
+ created_at: knex.raw('current_timestamp'),
25
+ created_by: meta.MIGRATION_USER
26
+ });
27
+ },
28
+ async function down(knex) {
29
+ logging.info('Deleting "Self-Serve Migration Integration"');
30
+
31
+ await knex('integrations').where({
32
+ type: 'core',
33
+ name: 'Self-Serve Migration Integration',
34
+ slug: 'self-serve-migration'
35
+ }).del();
36
+ }
37
+ );
@@ -0,0 +1,72 @@
1
+ const {InternalServerError} = require('@tryghost/errors');
2
+ const logging = require('@tryghost/logging');
3
+ const security = require('@tryghost/security');
4
+ const {default: ObjectID} = require('bson-objectid');
5
+ const {createTransactionalMigration, meta} = require('../../utils');
6
+
7
+ module.exports = createTransactionalMigration(
8
+ async function up(knex) {
9
+ logging.info('Adding Admin API key for "Self-Serve Migration Integration"');
10
+
11
+ const integration = await knex('integrations').where({
12
+ slug: 'self-serve-migration',
13
+ name: 'Self-Serve Migration Integration'
14
+ }).first();
15
+
16
+ if (!integration) {
17
+ throw new InternalServerError('Could not find "Self-Serve Migration Integration"');
18
+ }
19
+
20
+ const role = await knex('roles').where({
21
+ name: 'Self-Serve Migration Integration'
22
+ }).first();
23
+
24
+ if (!role) {
25
+ throw new InternalServerError('Could not find "Self-Serve Migration Integration" Role');
26
+ }
27
+
28
+ const existingKey = await knex('api_keys').where({
29
+ integration_id: integration.id,
30
+ role_id: role.id
31
+ }).first();
32
+
33
+ if (existingKey) {
34
+ logging.warn('Admin API key for "Self-Serve Migration Integration" already exists');
35
+ return;
36
+ }
37
+
38
+ await knex('api_keys').insert({
39
+ id: (new ObjectID()).toHexString(),
40
+ type: 'core',
41
+ secret: security.secret.create('admin'),
42
+ role_id: role.id,
43
+ integration_id: integration.id,
44
+ created_at: knex.raw('current_timestamp'),
45
+ created_by: meta.MIGRATION_USER
46
+ });
47
+ },
48
+ async function down(knex) {
49
+ logging.info('Removing "Self-Serve Migration Integration" API key');
50
+
51
+ const integration = await knex('integrations').where({
52
+ slug: 'self-serve-migration',
53
+ type: 'core',
54
+ name: 'Self-Serve Migration Integration'
55
+ }).first();
56
+
57
+ const role = await knex('roles').where({
58
+ name: 'Self-Serve Migration Integration'
59
+ }).first();
60
+
61
+ if (!role || !integration) {
62
+ logging.warn('Could not delete "Self-Serve Migration Integration" API key');
63
+ return;
64
+ }
65
+
66
+ logging.info('Deleting "Self-Serve Migration Integration" API Key');
67
+ await knex('api_keys').where({
68
+ integration_id: integration.id,
69
+ role_id: role.id
70
+ }).del();
71
+ }
72
+ );
@@ -1,5 +1,9 @@
1
1
  {
2
2
  "core": {
3
+ "last_mentions_report_email_timestamp": {
4
+ "defaultValue": null,
5
+ "type": "number"
6
+ },
3
7
  "db_hash": {
4
8
  "defaultValue": null,
5
9
  "type": "string"
@@ -76,6 +76,10 @@
76
76
  "name": "Ghost Explore Integration",
77
77
  "description": "Internal Integration for the Ghost Explore directory"
78
78
  },
79
+ {
80
+ "name": "Self-Serve Migration Integration",
81
+ "description": "Internal Integration for the Self-Serve migration tool"
82
+ },
79
83
  {
80
84
  "name": "DB Backup Integration",
81
85
  "description": "Internal DB Backup Client"
@@ -701,6 +705,18 @@
701
705
  "type": "core",
702
706
  "api_keys": [{"type": "admin", "role": "Ghost Explore Integration"}]
703
707
  },
708
+ {
709
+ "slug": "self-serve-migration",
710
+ "name": "Self-Serve Migration Integration",
711
+ "description": "Core Self-Serve Migration integration",
712
+ "type": "core",
713
+ "api_keys": [
714
+ {
715
+ "type": "admin",
716
+ "role": "Self-Serve Migration Integration"
717
+ }
718
+ ]
719
+ },
704
720
  {
705
721
  "slug": "ghost-backup",
706
722
  "name": "Ghost Backup",
@@ -780,6 +796,11 @@
780
796
  "Ghost Explore Integration": {
781
797
  "explore": "read"
782
798
  },
799
+ "Self-Serve Migration Integration": {
800
+ "db": "importContent",
801
+ "member": "add",
802
+ "tag": "read"
803
+ },
783
804
  "Admin Integration": {
784
805
  "mail": "all",
785
806
  "notification": "all",
@@ -37,6 +37,10 @@ module.exports = {
37
37
  footer_content: {type: 'text', maxlength: 1000000000, nullable: true},
38
38
  show_badge: {type: 'boolean', nullable: false, defaultTo: true},
39
39
  show_header_name: {type: 'boolean', nullable: false, defaultTo: true},
40
+ show_post_title_section: {type: 'boolean', nullable: false, defaultTo: true},
41
+ show_comment_cta: {type: 'boolean', nullable: false, defaultTo: true},
42
+ show_subscription_details: {type: 'boolean', nullable: false, defaultTo: false},
43
+ show_latest_posts: {type: 'boolean', nullable: false, defaultTo: false},
40
44
  created_at: {type: 'dateTime', nullable: false},
41
45
  updated_at: {type: 'dateTime', nullable: true}
42
46
  },
@@ -48,15 +48,19 @@ async function errorIfInvalidUrl(options) {
48
48
 
49
49
  // same as our normal request lib but if any request in a redirect chain resolves
50
50
  // to a private IP address it will be blocked before the request is made.
51
- const externalRequest = got.extend({
51
+ const gotOpts = {
52
52
  headers: {
53
53
  'user-agent': 'Ghost(https://github.com/TryGhost/Ghost)'
54
54
  },
55
55
  timeout: 10000, // default is no timeout
56
56
  hooks: {
57
- beforeRequest: [errorIfInvalidUrl,errorIfHostnameResolvesToPrivateIp],
57
+ beforeRequest: [errorIfInvalidUrl, errorIfHostnameResolvesToPrivateIp],
58
58
  beforeRedirect: [errorIfHostnameResolvesToPrivateIp]
59
59
  }
60
- });
60
+ };
61
61
 
62
- module.exports = externalRequest;
62
+ if (process.env.NODE_ENV?.startsWith('test')) {
63
+ gotOpts.retry = 0;
64
+ }
65
+
66
+ module.exports = got.extend(gotOpts);
@@ -1,3 +1,5 @@
1
+ const errors = require('@tryghost/errors');
2
+
1
3
  /**
2
4
  * @param {import('bookshelf')} Bookshelf
3
5
  */
@@ -9,6 +11,7 @@ module.exports = function (Bookshelf) {
9
11
  * @param {string} name Name of the relation to load
10
12
  * @param {Object} [options] Options to pass to the fetch when not yet loaded (or when force refreshing)
11
13
  * @param {boolean} [options.forceRefresh] If true, the relation will be fetched again even if it has already been loaded.
14
+ * @param {boolean} [options.require] Off by default. Throws an error if relation is not found.
12
15
  * @returns {Promise<import('bookshelf').Model|import('bookshelf').Collection|null>}
13
16
  */
14
17
  getLazyRelation: async function (name, options = {}) {
@@ -18,13 +21,32 @@ module.exports = function (Bookshelf) {
18
21
  }
19
22
 
20
23
  if (!this[name]) {
24
+ if (options.require) {
25
+ throw new errors.NotFoundError();
26
+ }
21
27
  return undefined;
22
28
  }
29
+
30
+ // Explicitly set require to false if it's not set, because default for .fetch is true (not false)
31
+ if (options.require) {
32
+ options.require = true;
33
+ } else {
34
+ options.require = false;
35
+ }
36
+
23
37
  // Not yet loaded, or force refresh
24
38
  // Note that we don't use .refresh on the relation on options.forceRefresh
25
39
  // Because the relation can also be a collection, which doesn't have a refresh method
26
40
  const instance = this[name]();
27
41
  await instance.fetch(options);
42
+
43
+ if (!instance.id && !(instance instanceof Bookshelf.Collection)) {
44
+ // Some weird behaviour in Bookshelf allows to just return a newly created model instance instead of throwing an error
45
+ if (options.require) {
46
+ throw new errors.NotFoundError();
47
+ }
48
+ return undefined;
49
+ }
28
50
  this.relations[name] = instance;
29
51
  return instance;
30
52
  }
@@ -41,7 +41,7 @@ module.exports = function (Bookshelf) {
41
41
  case 'add':
42
42
  return baseOptions.concat(extraOptions, ['autoRefresh']);
43
43
  case 'edit':
44
- return baseOptions.concat(extraOptions, ['id', 'require']);
44
+ return baseOptions.concat(extraOptions, ['id', 'require', 'autoRefresh']);
45
45
  case 'findOne':
46
46
  return baseOptions.concat(extraOptions, ['columns', 'require', 'mongoTransformer']);
47
47
  case 'findAll':
@@ -22,6 +22,10 @@ const Newsletter = ghostBookshelf.Model.extend({
22
22
  show_header_icon: true,
23
23
  show_header_title: true,
24
24
  show_header_name: true,
25
+ show_post_title_section: true,
26
+ show_comment_cta: true,
27
+ show_subscription_details: false,
28
+ show_latest_posts: false,
25
29
  feedback_enabled: false
26
30
  };
27
31
  },
@@ -25,6 +25,7 @@ class EmailServiceWrapper {
25
25
  const sentry = require('../../../shared/sentry');
26
26
  const membersRepository = membersService.api.members;
27
27
  const limitService = require('../limits');
28
+ const labs = require('../../../shared/labs');
28
29
 
29
30
  const mobiledocLib = require('../../lib/mobiledoc');
30
31
  const lexicalLib = require('../../lib/lexical');
@@ -68,7 +69,9 @@ class EmailServiceWrapper {
68
69
  linkTracking,
69
70
  memberAttributionService: memberAttribution.service,
70
71
  audienceFeedbackService: audienceFeedback.service,
71
- outboundLinkTagger: memberAttribution.outboundLinkTagger
72
+ outboundLinkTagger: memberAttribution.outboundLinkTagger,
73
+ labs,
74
+ models: {Post}
72
75
  });
73
76
 
74
77
  const sendingService = new SendingService({
@@ -0,0 +1,14 @@
1
+ //const debug = require('@tryghost/debug')('i18n');
2
+ const i18n = require('@tryghost/i18n');
3
+
4
+ module.exports.init = function () {
5
+ //const events = require('../lib/common/events');
6
+ //const settingsCache = require('../../shared/settings-cache');
7
+
8
+ module.exports = i18n(/* settingsCache.get('locale') */ 'en', 'ghost');
9
+
10
+ /*events.on('settings.locale.edited', (model) => {
11
+ debug('locale changed, updating i18n to', model.get('value'));
12
+ i18nInstance.changeLanguage(model.get('value'));
13
+ });*/
14
+ };
@@ -2,6 +2,7 @@ const urlService = require('../url');
2
2
  const urlUtils = require('../../../shared/url-utils');
3
3
  const settingsCache = require('../../../shared/settings-cache');
4
4
  const labs = require('../../../shared/labs');
5
+ const config = require('../../../shared/config');
5
6
 
6
7
  class MemberAttributionServiceWrapper {
7
8
  init() {
@@ -35,7 +36,7 @@ class MemberAttributionServiceWrapper {
35
36
 
36
37
  this.outboundLinkTagger = new OutboundLinkTagger({
37
38
  isEnabled: () => !labs.isSet('outboundLinkTagging') || !!settingsCache.get('outbound_link_tagging'),
38
- getSiteTitle: () => settingsCache.get('title'),
39
+ getSiteUrl: () => config.getSiteUrl(),
39
40
  urlUtils
40
41
  });
41
42