ghost 5.19.3 → 5.20.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 (144) hide show
  1. package/components/tryghost-adapter-manager-5.20.0.tgz +0 -0
  2. package/components/{tryghost-api-framework-5.19.3.tgz → tryghost-api-framework-5.20.0.tgz} +0 -0
  3. package/components/tryghost-api-version-compatibility-service-5.20.0.tgz +0 -0
  4. package/components/tryghost-audience-feedback-5.20.0.tgz +0 -0
  5. package/components/tryghost-bootstrap-socket-5.20.0.tgz +0 -0
  6. package/components/tryghost-constants-5.20.0.tgz +0 -0
  7. package/components/{tryghost-custom-theme-settings-service-5.19.3.tgz → tryghost-custom-theme-settings-service-5.20.0.tgz} +0 -0
  8. package/components/tryghost-domain-events-5.20.0.tgz +0 -0
  9. package/components/tryghost-email-analytics-provider-mailgun-5.20.0.tgz +0 -0
  10. package/components/tryghost-email-analytics-service-5.20.0.tgz +0 -0
  11. package/components/tryghost-email-content-generator-5.20.0.tgz +0 -0
  12. package/components/tryghost-express-dynamic-redirects-5.20.0.tgz +0 -0
  13. package/components/tryghost-extract-api-key-5.20.0.tgz +0 -0
  14. package/components/tryghost-html-to-plaintext-5.20.0.tgz +0 -0
  15. package/components/{tryghost-job-manager-5.19.3.tgz → tryghost-job-manager-5.20.0.tgz} +0 -0
  16. package/components/tryghost-link-redirects-5.20.0.tgz +0 -0
  17. package/components/tryghost-link-replacer-5.20.0.tgz +0 -0
  18. package/components/tryghost-link-tracking-5.20.0.tgz +0 -0
  19. package/components/tryghost-magic-link-5.20.0.tgz +0 -0
  20. package/components/tryghost-mailgun-client-5.20.0.tgz +0 -0
  21. package/components/tryghost-member-analytics-service-5.20.0.tgz +0 -0
  22. package/components/tryghost-member-attribution-5.20.0.tgz +0 -0
  23. package/components/tryghost-member-events-5.20.0.tgz +0 -0
  24. package/components/tryghost-members-analytics-ingress-5.20.0.tgz +0 -0
  25. package/components/tryghost-members-api-5.20.0.tgz +0 -0
  26. package/components/tryghost-members-csv-5.20.0.tgz +0 -0
  27. package/components/tryghost-members-events-service-5.20.0.tgz +0 -0
  28. package/components/tryghost-members-importer-5.20.0.tgz +0 -0
  29. package/components/tryghost-members-offers-5.20.0.tgz +0 -0
  30. package/components/tryghost-members-payments-5.20.0.tgz +0 -0
  31. package/components/tryghost-members-ssr-5.20.0.tgz +0 -0
  32. package/components/tryghost-members-stripe-service-5.20.0.tgz +0 -0
  33. package/components/tryghost-minifier-5.20.0.tgz +0 -0
  34. package/components/tryghost-mw-api-version-mismatch-5.20.0.tgz +0 -0
  35. package/components/tryghost-mw-cache-control-5.20.0.tgz +0 -0
  36. package/components/{tryghost-mw-error-handler-5.19.3.tgz → tryghost-mw-error-handler-5.20.0.tgz} +0 -0
  37. package/components/tryghost-mw-session-from-token-5.20.0.tgz +0 -0
  38. package/components/tryghost-mw-update-user-last-seen-5.20.0.tgz +0 -0
  39. package/components/tryghost-mw-vhost-5.20.0.tgz +0 -0
  40. package/components/tryghost-oembed-service-5.20.0.tgz +0 -0
  41. package/components/tryghost-package-json-5.20.0.tgz +0 -0
  42. package/components/{tryghost-referrers-5.19.3.tgz → tryghost-referrers-5.20.0.tgz} +0 -0
  43. package/components/tryghost-security-5.20.0.tgz +0 -0
  44. package/components/tryghost-session-service-5.20.0.tgz +0 -0
  45. package/components/tryghost-settings-path-manager-5.20.0.tgz +0 -0
  46. package/components/tryghost-staff-service-5.20.0.tgz +0 -0
  47. package/components/tryghost-stats-service-5.20.0.tgz +0 -0
  48. package/components/tryghost-tiers-5.20.0.tgz +0 -0
  49. package/components/tryghost-update-check-service-5.20.0.tgz +0 -0
  50. package/components/tryghost-verification-trigger-5.20.0.tgz +0 -0
  51. package/components/tryghost-version-notifications-data-service-5.20.0.tgz +0 -0
  52. package/core/built/admin/assets/{chunk.143.c035c61595ed02eee886.js → chunk.143.d245b085ad1efed4ee76.js} +7 -7
  53. package/core/built/admin/assets/{chunk.178.998dfbcebcec635146b1.js → chunk.178.c45f56ea31775e509497.js} +4 -4
  54. package/core/built/admin/assets/{chunk.613.f1d519ad47e7f9024263.js → chunk.613.c4d89dc2d28c1b20348f.js} +3 -3
  55. package/core/built/admin/assets/{chunk.613.f1d519ad47e7f9024263.js.LICENSE.txt → chunk.613.c4d89dc2d28c1b20348f.js.LICENSE.txt} +0 -0
  56. package/core/built/admin/assets/{ghost-5ce6f5a730c83c91fc258b12c537ea35.js → ghost-07e4bbf5029630b3c8a8a50c4b9f2d9e.js} +2746 -2658
  57. package/core/built/admin/assets/{ghost-dark-41929e4857de411a23597a9de49a4e4f.css → ghost-dark-363185f15c782b4b8394c5db23984e7f.css} +1 -1
  58. package/core/built/admin/assets/{ghost-982146a4ada3a5af1981d1919ae01d08.css → ghost-fd0480352bf27e013b2b00a1bf9ffe84.css} +1 -1
  59. package/core/built/admin/assets/{vendor-5c7d7063620bec13668c4370145cd4b4.js → vendor-518b03b02df9a55706d150627ef1004f.js} +84 -72
  60. package/core/built/admin/index.html +7 -7
  61. package/core/server/api/endpoints/links.js +33 -1
  62. package/core/server/api/endpoints/members.js +1 -4
  63. package/core/server/api/endpoints/posts-public.js +1 -1
  64. package/core/server/api/endpoints/posts.js +2 -1
  65. package/core/server/api/endpoints/utils/serializers/input/posts.js +21 -1
  66. package/core/server/api/endpoints/utils/serializers/output/index.js +4 -0
  67. package/core/server/api/endpoints/utils/serializers/output/links.js +5 -0
  68. package/core/server/api/endpoints/utils/serializers/output/mappers/activity-feed-events.js +47 -15
  69. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +15 -2
  70. package/core/server/api/endpoints/utils/serializers/output/mappers/snippets.js +2 -2
  71. package/core/server/api/endpoints/utils/serializers/output/members.js +6 -5
  72. package/core/server/data/importer/importers/data/custom-theme-settings.js +81 -0
  73. package/core/server/data/importer/importers/data/data-importer.js +2 -0
  74. package/core/server/data/migrations/utils/permissions.js +35 -24
  75. package/core/server/data/migrations/versions/5.20/2022-10-18-05-39-drop-nullable-tier-id.js +3 -0
  76. package/core/server/data/migrations/versions/5.20/2022-10-18-10-13-add-ghost-subscription-id-column-to-mscs.js +10 -0
  77. package/core/server/data/migrations/versions/5.20/2022-10-19-11-17-add-link-browse-permissions.js +10 -0
  78. package/core/server/data/migrations/versions/5.20/2022-10-20-02-52-add-link-edit-permissions.js +10 -0
  79. package/core/server/data/schema/commands.js +107 -48
  80. package/core/server/data/schema/fixtures/fixtures.json +14 -2
  81. package/core/server/data/schema/schema.js +2 -1
  82. package/core/server/models/base/plugins/actions.js +1 -1
  83. package/core/server/models/email-recipient.js +14 -0
  84. package/core/server/models/email.js +1 -5
  85. package/core/server/models/member-click-event.js +24 -0
  86. package/core/server/models/member-paid-subscription-event.js +15 -0
  87. package/core/server/models/post.js +32 -1
  88. package/core/server/models/redirect.js +1 -0
  89. package/core/server/services/audience-feedback/index.js +2 -0
  90. package/core/server/services/link-redirection/LinkRedirectRepository.js +16 -5
  91. package/core/server/services/link-tracking/PostLinkRepository.js +26 -2
  92. package/core/server/services/link-tracking/index.js +3 -1
  93. package/core/server/services/members/api.js +2 -1
  94. package/core/server/services/url/UrlGenerator.js +4 -2
  95. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  96. package/core/shared/config/defaults.json +1 -1
  97. package/core/shared/labs.js +3 -3
  98. package/package.json +98 -97
  99. package/yarn.lock +28 -43
  100. package/components/tryghost-adapter-manager-5.19.3.tgz +0 -0
  101. package/components/tryghost-api-version-compatibility-service-5.19.3.tgz +0 -0
  102. package/components/tryghost-audience-feedback-5.19.3.tgz +0 -0
  103. package/components/tryghost-bootstrap-socket-5.19.3.tgz +0 -0
  104. package/components/tryghost-constants-5.19.3.tgz +0 -0
  105. package/components/tryghost-domain-events-5.19.3.tgz +0 -0
  106. package/components/tryghost-email-analytics-provider-mailgun-5.19.3.tgz +0 -0
  107. package/components/tryghost-email-analytics-service-5.19.3.tgz +0 -0
  108. package/components/tryghost-email-content-generator-5.19.3.tgz +0 -0
  109. package/components/tryghost-express-dynamic-redirects-5.19.3.tgz +0 -0
  110. package/components/tryghost-extract-api-key-5.19.3.tgz +0 -0
  111. package/components/tryghost-html-to-plaintext-5.19.3.tgz +0 -0
  112. package/components/tryghost-link-redirects-5.19.3.tgz +0 -0
  113. package/components/tryghost-link-replacer-5.19.3.tgz +0 -0
  114. package/components/tryghost-link-tracking-5.19.3.tgz +0 -0
  115. package/components/tryghost-magic-link-5.19.3.tgz +0 -0
  116. package/components/tryghost-mailgun-client-5.19.3.tgz +0 -0
  117. package/components/tryghost-member-analytics-service-5.19.3.tgz +0 -0
  118. package/components/tryghost-member-attribution-5.19.3.tgz +0 -0
  119. package/components/tryghost-member-events-5.19.3.tgz +0 -0
  120. package/components/tryghost-members-analytics-ingress-5.19.3.tgz +0 -0
  121. package/components/tryghost-members-api-5.19.3.tgz +0 -0
  122. package/components/tryghost-members-csv-5.19.3.tgz +0 -0
  123. package/components/tryghost-members-events-service-5.19.3.tgz +0 -0
  124. package/components/tryghost-members-importer-5.19.3.tgz +0 -0
  125. package/components/tryghost-members-offers-5.19.3.tgz +0 -0
  126. package/components/tryghost-members-payments-5.19.3.tgz +0 -0
  127. package/components/tryghost-members-ssr-5.19.3.tgz +0 -0
  128. package/components/tryghost-members-stripe-service-5.19.3.tgz +0 -0
  129. package/components/tryghost-minifier-5.19.3.tgz +0 -0
  130. package/components/tryghost-mw-api-version-mismatch-5.19.3.tgz +0 -0
  131. package/components/tryghost-mw-cache-control-5.19.3.tgz +0 -0
  132. package/components/tryghost-mw-session-from-token-5.19.3.tgz +0 -0
  133. package/components/tryghost-mw-update-user-last-seen-5.19.3.tgz +0 -0
  134. package/components/tryghost-mw-vhost-5.19.3.tgz +0 -0
  135. package/components/tryghost-oembed-service-5.19.3.tgz +0 -0
  136. package/components/tryghost-package-json-5.19.3.tgz +0 -0
  137. package/components/tryghost-security-5.19.3.tgz +0 -0
  138. package/components/tryghost-session-service-5.19.3.tgz +0 -0
  139. package/components/tryghost-settings-path-manager-5.19.3.tgz +0 -0
  140. package/components/tryghost-staff-service-5.19.3.tgz +0 -0
  141. package/components/tryghost-stats-service-5.19.3.tgz +0 -0
  142. package/components/tryghost-update-check-service-5.19.3.tgz +0 -0
  143. package/components/tryghost-verification-trigger-5.19.3.tgz +0 -0
  144. package/components/tryghost-version-notifications-data-service-5.19.3.tgz +0 -0
@@ -8,7 +8,7 @@
8
8
  <title>Ghost Admin</title>
9
9
 
10
10
 
11
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.19%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.20%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -37,7 +37,7 @@
37
37
  </style>
38
38
 
39
39
  <link integrity="" rel="stylesheet" href="assets/vendor-3e6947aa681f0fb82b193090e520dc73.css">
40
- <link integrity="" rel="stylesheet" href="assets/ghost-982146a4ada3a5af1981d1919ae01d08.css" title="light">
40
+ <link integrity="" rel="stylesheet" href="assets/ghost-fd0480352bf27e013b2b00a1bf9ffe84.css" title="light">
41
41
 
42
42
 
43
43
  </head>
@@ -45,7 +45,7 @@
45
45
 
46
46
  <div class="ember-load-indicator">
47
47
  <div class="gh-loading-content">
48
- <video width="100" height="100" loop="" autoplay="" muted="" playsinline="" preload="metadata">
48
+ <video width="100" height="100" loop autoplay muted playsinline preload="metadata" style="width: 100px; height: 100px;">
49
49
  <source src="assets/videos/logo-loader.mp4" type="video/mp4" />
50
50
  <div class="gh-loading-spinner"></div>
51
51
  </video>
@@ -56,9 +56,9 @@
56
56
 
57
57
  <div id="ember-basic-dropdown-wormhole"></div>
58
58
 
59
- <script src="assets/vendor-5c7d7063620bec13668c4370145cd4b4.js"></script>
60
- <script src="assets/chunk.613.f1d519ad47e7f9024263.js"></script>
61
- <script src="assets/chunk.143.c035c61595ed02eee886.js"></script>
62
- <script src="assets/ghost-5ce6f5a730c83c91fc258b12c537ea35.js"></script>
59
+ <script src="assets/vendor-518b03b02df9a55706d150627ef1004f.js"></script>
60
+ <script src="assets/chunk.613.c4d89dc2d28c1b20348f.js"></script>
61
+ <script src="assets/chunk.143.d245b085ad1efed4ee76.js"></script>
62
+ <script src="assets/ghost-07e4bbf5029630b3c8a8a50c4b9f2d9e.js"></script>
63
63
  </body>
64
64
  </html>
@@ -6,7 +6,7 @@ module.exports = {
6
6
  options: [
7
7
  'filter'
8
8
  ],
9
- permissions: false,
9
+ permissions: true,
10
10
  async query(frame) {
11
11
  const links = await linkTrackingService.service.getLinks(frame.options);
12
12
 
@@ -21,5 +21,37 @@ module.exports = {
21
21
  }
22
22
  };
23
23
  }
24
+ },
25
+ bulkEdit: {
26
+ statusCode: 200,
27
+ headers: {
28
+ cacheInvalidate: true
29
+ },
30
+ options: [
31
+ 'filter'
32
+ ],
33
+ data: [
34
+ 'action',
35
+ 'meta'
36
+ ],
37
+ validation: {
38
+ data: {
39
+ action: {
40
+ required: true,
41
+ values: ['updateLink']
42
+ }
43
+ },
44
+ options: {
45
+ filter: {
46
+ required: true
47
+ }
48
+ }
49
+ },
50
+ permissions: {
51
+ method: 'edit'
52
+ },
53
+ async query(frame) {
54
+ return await linkTrackingService.service.bulkEdit(frame.data.bulk, frame.options);
55
+ }
24
56
  }
25
57
  };
@@ -435,10 +435,7 @@ module.exports = {
435
435
  method: 'browse'
436
436
  },
437
437
  async query(frame) {
438
- const events = await membersService.api.events.getEventTimeline(frame.options);
439
- return {
440
- events
441
- };
438
+ return await membersService.api.events.getEventTimeline(frame.options);
442
439
  }
443
440
  }
444
441
  };
@@ -1,7 +1,7 @@
1
1
  const models = require('../../models');
2
2
  const tpl = require('@tryghost/tpl');
3
3
  const errors = require('@tryghost/errors');
4
- const allowedIncludes = ['tags', 'authors', 'tiers'];
4
+ const allowedIncludes = ['tags', 'authors', 'tiers', 'sentiment'];
5
5
 
6
6
  const messages = {
7
7
  postNotFound: 'Post not found.'
@@ -11,7 +11,8 @@ const allowedIncludes = [
11
11
  'newsletter',
12
12
  'count.signups',
13
13
  'count.paid_conversions',
14
- 'count.clicks'
14
+ 'count.clicks',
15
+ 'sentiment'
15
16
  ];
16
17
  const unsafeAttrs = ['status', 'authors', 'visibility'];
17
18
 
@@ -16,7 +16,26 @@ function removeSourceFormats(frame) {
16
16
  }
17
17
  }
18
18
 
19
+ /**
20
+ * Map names of relations to the internal names
21
+ */
22
+ function mapWithRelated(frame) {
23
+ if (frame.options.withRelated) {
24
+ // Map sentiment to count.sentiment
25
+ if (labs.isSet('audienceFeedback')) {
26
+ frame.options.withRelated = frame.options.withRelated.map((relation) => {
27
+ return relation === 'sentiment' ? 'count.sentiment' : relation;
28
+ });
29
+ }
30
+ return;
31
+ }
32
+ }
33
+
19
34
  function defaultRelations(frame) {
35
+ // Apply same mapping as content API
36
+ mapWithRelated(frame);
37
+
38
+ // Addditional defaults for admin API
20
39
  if (frame.options.withRelated) {
21
40
  return;
22
41
  }
@@ -26,7 +45,7 @@ function defaultRelations(frame) {
26
45
  }
27
46
 
28
47
  if (labs.isSet('audienceFeedback')) {
29
- frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter', 'count.signups', 'count.paid_conversions', 'count.clicks', 'count.sentiment', 'count.positive_feedback'];
48
+ frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter', 'count.conversions', 'count.clicks', 'count.sentiment', 'count.positive_feedback'];
30
49
  } else {
31
50
  frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter', 'count.signups', 'count.paid_conversions', 'count.clicks'];
32
51
  }
@@ -111,6 +130,7 @@ module.exports = {
111
130
 
112
131
  setDefaultOrder(frame);
113
132
  forceVisibilityColumn(frame);
133
+ mapWithRelated(frame);
114
134
  }
115
135
 
116
136
  if (!localUtils.isContentAPI(frame)) {
@@ -127,5 +127,9 @@ module.exports = {
127
127
 
128
128
  get members_stripe_connect() {
129
129
  return require('./members-stripe-connect');
130
+ },
131
+
132
+ get links() {
133
+ return require('./links');
130
134
  }
131
135
  };
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ bulkEdit(data, apiConfig, frame) {
3
+ frame.response = data;
4
+ }
5
+ };
@@ -2,6 +2,21 @@ const mapComment = require('./comments');
2
2
  const url = require('../utils/url');
3
3
  const _ = require('lodash');
4
4
 
5
+ const memberFields = [
6
+ 'id',
7
+ 'uuid',
8
+ 'name',
9
+ 'email',
10
+ 'avatar_image'
11
+ ];
12
+
13
+ const postFields = [
14
+ 'id',
15
+ 'uuid',
16
+ 'title',
17
+ 'url'
18
+ ];
19
+
5
20
  const commentEventMapper = (json, frame) => {
6
21
  return {
7
22
  ...json,
@@ -10,26 +25,11 @@ const commentEventMapper = (json, frame) => {
10
25
  };
11
26
 
12
27
  const clickEventMapper = (json, frame) => {
13
- const memberFields = [
14
- 'id',
15
- 'uuid',
16
- 'name',
17
- 'email',
18
- 'avatar_image'
19
- ];
20
-
21
28
  const linkFields = [
22
29
  'from',
23
30
  'to'
24
31
  ];
25
32
 
26
- const postFields = [
27
- 'id',
28
- 'uuid',
29
- 'title',
30
- 'url'
31
- ];
32
-
33
33
  const data = json.data;
34
34
  const response = {};
35
35
 
@@ -59,6 +59,35 @@ const clickEventMapper = (json, frame) => {
59
59
  };
60
60
  };
61
61
 
62
+ const feedbackEventMapper = (json, frame) => {
63
+ const feedbackFields = [
64
+ 'id',
65
+ 'score',
66
+ 'created_at'
67
+ ];
68
+
69
+ const data = json.data;
70
+ const response = _.pick(data, feedbackFields);
71
+
72
+ if (data.post) {
73
+ url.forPost(data.post.id, data.post, frame);
74
+ response.post = _.pick(data.post, postFields);
75
+ } else {
76
+ response.post = null;
77
+ }
78
+
79
+ if (data.member) {
80
+ response.member = _.pick(data.member, memberFields);
81
+ } else {
82
+ response.member = null;
83
+ }
84
+
85
+ return {
86
+ ...json,
87
+ data: response
88
+ };
89
+ };
90
+
62
91
  function serializeAttribution(attribution) {
63
92
  if (!attribution) {
64
93
  return attribution;
@@ -82,6 +111,9 @@ const activityFeedMapper = (event, frame) => {
82
111
  if (event.type === 'click_event') {
83
112
  return clickEventMapper(event, frame);
84
113
  }
114
+ if (event.type === 'feedback_event') {
115
+ return feedbackEventMapper(event, frame);
116
+ }
85
117
  if (event.data?.attribution) {
86
118
  event.data.attribution = serializeAttribution(event.data.attribution);
87
119
  }
@@ -119,8 +119,21 @@ module.exports = async (model, frame, options = {}) => {
119
119
  );
120
120
  }
121
121
 
122
- if (jsonModel.count && !jsonModel.count.sentiment) {
123
- jsonModel.count.sentiment = 0;
122
+ // The sentiment has been loaded as a count relation in count.sentiment. But externally in the API we use just 'sentiment' instead of count.sentiment
123
+ // This part moves count.sentiment to just 'sentiment' when it has been loaded
124
+ if (frame.options.withRelated && frame.options.withRelated.includes('count.sentiment')) {
125
+ if (!jsonModel.count) {
126
+ jsonModel.sentiment = 0;
127
+ } else {
128
+ jsonModel.sentiment = jsonModel.count.sentiment ?? 0;
129
+
130
+ // Delete it from the original location
131
+ delete jsonModel.count.sentiment;
132
+
133
+ if (Object.keys(jsonModel.count).length === 0) {
134
+ delete jsonModel.count;
135
+ }
136
+ }
124
137
  }
125
138
 
126
139
  if (jsonModel.count && !jsonModel.count.positive_feedback) {
@@ -22,8 +22,8 @@ module.exports = (snippet, frame) => {
22
22
  /**
23
23
  * @typedef {Object} SerializedSnippet
24
24
  * @prop {string} id
25
- * @prop {string=} name
26
- * @prop {string=} mobiledoc
25
+ * @prop {string} [name]
26
+ * @prop {string} [mobiledoc]
27
27
  * @prop {string} created_at
28
28
  * @prop {string} updated_at
29
29
  * @prop {string} created_by
@@ -76,11 +76,12 @@ function bulkAction(bulkActionResult, _apiConfig, frame) {
76
76
 
77
77
  /**
78
78
  *
79
- * @returns {{events: any[]}}
79
+ * @returns {{events: any[], meta: any}}
80
80
  */
81
81
  function activityFeed(data, _apiConfig, frame) {
82
82
  return {
83
- events: data.events.map(e => mappers.activityFeedEvents(e, frame))
83
+ events: data.events.map(e => mappers.activityFeedEvents(e, frame)),
84
+ meta: data.meta
84
85
  };
85
86
  }
86
87
 
@@ -216,15 +217,15 @@ function createSerializer(debugString, serialize) {
216
217
  * @prop {string} id
217
218
  * @prop {string} uuid
218
219
  * @prop {string} email
219
- * @prop {string=} name
220
- * @prop {string=} note
220
+ * @prop {string} [name]
221
+ * @prop {string} [note]
221
222
  * @prop {null|string} geolocation
222
223
  * @prop {boolean} subscribed
223
224
  * @prop {string} created_at
224
225
  * @prop {string} updated_at
225
226
  * @prop {string[]} labels
226
227
  * @prop {SerializedMemberStripeSubscription[]} subscriptions
227
- * @prop {SerializedMemberProduct[]=} products
228
+ * @prop {SerializedMemberProduct[]} [products]
228
229
  * @prop {string} avatar_image
229
230
  * @prop {boolean} comped
230
231
  * @prop {number} email_count
@@ -0,0 +1,81 @@
1
+ const _ = require('lodash');
2
+ const Promise = require('bluebird');
3
+ const debug = require('@tryghost/debug')('importer:roles');
4
+ const BaseImporter = require('./base');
5
+ const models = require('../../../../models');
6
+ const {activate} = require('../../../../services/themes/activate');
7
+
8
+ class CustomThemeSettingsImporter extends BaseImporter {
9
+ constructor(allDataFromFile) {
10
+ super(allDataFromFile, {
11
+ modelName: 'CustomThemeSetting',
12
+ dataKeyToImport: 'custom_theme_settings'
13
+ });
14
+ }
15
+
16
+ beforeImport() {
17
+ debug('beforeImport');
18
+ return super.beforeImport();
19
+ }
20
+
21
+ doImport(options, importOptions) {
22
+ debug('doImport', this.modelName, this.dataToImport.length);
23
+
24
+ let ops = [];
25
+
26
+ _.each(this.dataToImport, (item) => {
27
+ ops.push(models.CustomThemeSetting.findOne({theme: item.theme, key: item.key}, options)
28
+ .then((setting) => {
29
+ if (_.isObject(item.value)) {
30
+ item.value = JSON.stringify(item.value);
31
+ }
32
+
33
+ if (setting) {
34
+ setting.set('value', item.value);
35
+ if (setting.hasChanged()) {
36
+ return setting.save(null, options)
37
+ .then((importedModel) => {
38
+ if (importOptions.returnImportedData) {
39
+ this.importedDataToReturn.push(importedModel.toJSON());
40
+ }
41
+ return importedModel;
42
+ })
43
+ .catch((err) => {
44
+ return this.handleError(err, item);
45
+ });
46
+ }
47
+
48
+ return Promise.resolve();
49
+ }
50
+
51
+ return models.CustomThemeSetting.add(item, options)
52
+ .then((importedModel) => {
53
+ if (importOptions.returnImportedData) {
54
+ this.importedDataToReturn.push(importedModel.toJSON());
55
+ }
56
+ return importedModel;
57
+ })
58
+ .catch((err) => {
59
+ return this.handleError(err, item);
60
+ });
61
+ })
62
+ .reflect());
63
+ });
64
+
65
+ const opsPromise = Promise.all(ops);
66
+
67
+ // activate function is called to refresh cache when importing custom theme settings for active theme
68
+ opsPromise.then(() => {
69
+ models.Settings.findOne({key: 'active_theme'})
70
+ .then((theme) => {
71
+ const currentTheme = theme.get('value');
72
+ if (this.dataToImport.some(themeSetting => themeSetting.theme === currentTheme)) {
73
+ activate(currentTheme);
74
+ }
75
+ });
76
+ });
77
+
78
+ return opsPromise;
79
+ }
80
+ }
81
+ module.exports = CustomThemeSettingsImporter;
@@ -13,6 +13,7 @@ const NewslettersImporter = require('./newsletters');
13
13
  const ProductsImporter = require('./products');
14
14
  const StripeProductsImporter = require('./stripe-products');
15
15
  const StripePricesImporter = require('./stripe-prices');
16
+ const CustomThemeSettingsImporter = require('./custom-theme-settings');
16
17
  const RolesImporter = require('./roles');
17
18
  let importers = {};
18
19
  let DataImporter;
@@ -35,6 +36,7 @@ DataImporter = {
35
36
  importers.stripe_products = new StripeProductsImporter(importData.data);
36
37
  importers.stripe_prices = new StripePricesImporter(importData.data);
37
38
  importers.posts = new PostsImporter(importData.data);
39
+ importers.custom_theme_settings = new CustomThemeSettingsImporter(importData.data);
38
40
 
39
41
  return importData;
40
42
  },
@@ -10,6 +10,10 @@ const messages = {
10
10
  permissionRoleActionError: 'Cannot {action} permission({permission}) with role({role}) - {resource} does not exist'
11
11
  };
12
12
 
13
+ /**
14
+ * @param {import('knex').Knex} connection
15
+ * @param {PermissionConfig} config
16
+ */
13
17
  async function addPermissionHelper(connection, config) {
14
18
  const existingPermission = await connection('permissions').where({
15
19
  name: config.name,
@@ -38,6 +42,10 @@ async function addPermissionHelper(connection, config) {
38
42
  });
39
43
  }
40
44
 
45
+ /**
46
+ * @param {import('knex').Knex} connection
47
+ * @param {PermissionConfig} config
48
+ */
41
49
  async function removePermissionHelper(connection, config) {
42
50
  const existingPermission = await connection('permissions').where({
43
51
  name: config.name,
@@ -61,10 +69,7 @@ async function removePermissionHelper(connection, config) {
61
69
  /**
62
70
  * Creates a migration which will add a permission to the database
63
71
  *
64
- * @param {Object} config
65
- * @param {string} config.name - The name of the permission
66
- * @param {string} config.action - The action_type of the permission
67
- * @param {string} config.object - The object_type of the permission
72
+ * @param {PermissionConfig} config
68
73
  *
69
74
  * @returns {Migration}
70
75
  */
@@ -82,10 +87,7 @@ function addPermission(config) {
82
87
  /**
83
88
  * Creates a migration which will remove a permission from the database
84
89
  *
85
- * @param {Object} config
86
- * @param {string} config.name - The name of the permission
87
- * @param {string} config.action - The action_type of the permission
88
- * @param {string} config.object - The object_type of the permission
90
+ * @param {PermissionConfig} config
89
91
  *
90
92
  * @returns {Migration}
91
93
  */
@@ -100,6 +102,10 @@ function removePermission(config) {
100
102
  );
101
103
  }
102
104
 
105
+ /**
106
+ * @param {import('knex').Knex} connection
107
+ * @param {PermissionRoleConfig} config
108
+ */
103
109
  async function addPermissionToRoleHelper(connection, config) {
104
110
  const permission = await connection('permissions').where({
105
111
  name: config.permission
@@ -149,6 +155,10 @@ async function addPermissionToRoleHelper(connection, config) {
149
155
  });
150
156
  }
151
157
 
158
+ /**
159
+ * @param {import('knex').Knex} connection
160
+ * @param {PermissionRoleConfig} config
161
+ */
152
162
  async function removePermissionFromRoleHelper(connection, config) {
153
163
  const permission = await connection('permissions').where({
154
164
  name: config.permission
@@ -188,9 +198,7 @@ async function removePermissionFromRoleHelper(connection, config) {
188
198
  /**
189
199
  * Creates a migration which will link a permission to a role in the database
190
200
  *
191
- * @param {Object} config
192
- * @param {string} config.permission - The name of the permission
193
- * @param {string} config.role - The name of the role
201
+ * @param {PermissionRoleConfig} config
194
202
  *
195
203
  * @returns {Migration}
196
204
  */
@@ -208,9 +216,7 @@ function addPermissionToRole(config) {
208
216
  /**
209
217
  * Creates a migration which will remove the permission from roles
210
218
  *
211
- * @param {Object} config
212
- * @param {string} config.permission - The name of the permission
213
- * @param {string} config.role - The name of the role
219
+ * @param {PermissionRoleConfig} config
214
220
  *
215
221
  * @returns {Migration}
216
222
  */
@@ -228,11 +234,7 @@ function removePermissionFromRole(config) {
228
234
  /**
229
235
  * Creates a migration which will add a permission to the database, and then link it to roles
230
236
  *
231
- * @param {Object} config
232
- * @param {string} config.name - The name of the permission
233
- * @param {string} config.action - The action_type of the permission
234
- * @param {string} config.object - The object_type of the permission
235
- *
237
+ * @param {PermissionConfig} config
236
238
  * @param {string[]} roles - A list of role names
237
239
  *
238
240
  * @returns {Migration}
@@ -247,11 +249,7 @@ function addPermissionWithRoles(config, roles) {
247
249
  /**
248
250
  * Creates a migration which will remove permissions from roles, and then remove the permission
249
251
  *
250
- * @param {Object} config
251
- * @param {string} config.name - The name of the permission
252
- * @param {string} config.action - The action_type of the permission
253
- * @param {string} config.object - The object_type of the permission
254
- *
252
+ * @param {PermissionConfig} config
255
253
  * @param {string[]} roles - A list of role names
256
254
  *
257
255
  * @returns {Migration}
@@ -270,6 +268,19 @@ module.exports = {
270
268
  createRemovePermissionMigration
271
269
  };
272
270
 
271
+ /**
272
+ * @typedef {Object} PermissionConfig
273
+ * @prop {string} config.name - The name of the permission
274
+ * @prop {string} config.action - The action_type of the permission
275
+ * @prop {string} config.object - The object_type of the permission
276
+ */
277
+
278
+ /**
279
+ * @typedef {Object} PermissionRoleConfig
280
+ * @prop {string} config.permission - The name of the permission
281
+ * @prop {string} config.role - The role to assign the Permission to
282
+ */
283
+
273
284
  /**
274
285
  * @typedef {Object} TransactionalMigrationFunctionOptions
275
286
  *
@@ -0,0 +1,3 @@
1
+ const {createDropNullableMigration} = require('../../utils');
2
+
3
+ module.exports = createDropNullableMigration('subscriptions', 'tier_id');
@@ -0,0 +1,10 @@
1
+ const {createAddColumnMigration} = require('../../utils');
2
+
3
+ module.exports = createAddColumnMigration('members_stripe_customers_subscriptions', 'ghost_subscription_id', {
4
+ type: 'string',
5
+ maxlength: 24,
6
+ nullable: true,
7
+ references: 'subscriptions.id',
8
+ constraintName: 'mscs_ghost_subscription_id_foreign',
9
+ cascadeDelete: true
10
+ });
@@ -0,0 +1,10 @@
1
+ const {addPermissionWithRoles} = require('../../utils');
2
+
3
+ module.exports = addPermissionWithRoles({
4
+ name: 'Browse links',
5
+ action: 'browse',
6
+ object: 'link'
7
+ }, [
8
+ 'Administrator',
9
+ 'Admin Integration'
10
+ ]);
@@ -0,0 +1,10 @@
1
+ const {addPermissionWithRoles} = require('../../utils');
2
+
3
+ module.exports = addPermissionWithRoles({
4
+ name: 'Edit links',
5
+ action: 'edit',
6
+ object: 'link'
7
+ }, [
8
+ 'Administrator',
9
+ 'Admin Integration'
10
+ ]);