ghost 5.19.3 → 5.21.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 (180) hide show
  1. package/components/tryghost-adapter-manager-5.21.0.tgz +0 -0
  2. package/components/{tryghost-api-framework-5.19.3.tgz → tryghost-api-framework-5.21.0.tgz} +0 -0
  3. package/components/tryghost-api-version-compatibility-service-5.21.0.tgz +0 -0
  4. package/components/tryghost-audience-feedback-5.21.0.tgz +0 -0
  5. package/components/tryghost-bootstrap-socket-5.21.0.tgz +0 -0
  6. package/components/tryghost-constants-5.21.0.tgz +0 -0
  7. package/components/tryghost-custom-theme-settings-service-5.21.0.tgz +0 -0
  8. package/components/tryghost-data-generator-5.21.0.tgz +0 -0
  9. package/components/tryghost-domain-events-5.21.0.tgz +0 -0
  10. package/components/tryghost-email-analytics-provider-mailgun-5.21.0.tgz +0 -0
  11. package/components/tryghost-email-analytics-service-5.21.0.tgz +0 -0
  12. package/components/tryghost-email-content-generator-5.21.0.tgz +0 -0
  13. package/components/tryghost-express-dynamic-redirects-5.21.0.tgz +0 -0
  14. package/components/tryghost-extract-api-key-5.21.0.tgz +0 -0
  15. package/components/tryghost-html-to-plaintext-5.21.0.tgz +0 -0
  16. package/components/{tryghost-job-manager-5.19.3.tgz → tryghost-job-manager-5.21.0.tgz} +0 -0
  17. package/components/tryghost-link-redirects-5.21.0.tgz +0 -0
  18. package/components/tryghost-link-replacer-5.21.0.tgz +0 -0
  19. package/components/tryghost-link-tracking-5.21.0.tgz +0 -0
  20. package/components/tryghost-magic-link-5.21.0.tgz +0 -0
  21. package/components/tryghost-mailgun-client-5.21.0.tgz +0 -0
  22. package/components/tryghost-member-analytics-service-5.21.0.tgz +0 -0
  23. package/components/tryghost-member-attribution-5.21.0.tgz +0 -0
  24. package/components/tryghost-member-events-5.21.0.tgz +0 -0
  25. package/components/tryghost-members-analytics-ingress-5.21.0.tgz +0 -0
  26. package/components/tryghost-members-api-5.21.0.tgz +0 -0
  27. package/components/tryghost-members-csv-5.21.0.tgz +0 -0
  28. package/components/tryghost-members-events-service-5.21.0.tgz +0 -0
  29. package/components/tryghost-members-importer-5.21.0.tgz +0 -0
  30. package/components/tryghost-members-offers-5.21.0.tgz +0 -0
  31. package/components/tryghost-members-payments-5.21.0.tgz +0 -0
  32. package/components/{tryghost-members-ssr-5.19.3.tgz → tryghost-members-ssr-5.21.0.tgz} +0 -0
  33. package/components/tryghost-members-stripe-service-5.21.0.tgz +0 -0
  34. package/components/tryghost-minifier-5.21.0.tgz +0 -0
  35. package/components/tryghost-mw-api-version-mismatch-5.21.0.tgz +0 -0
  36. package/components/tryghost-mw-cache-control-5.21.0.tgz +0 -0
  37. package/components/tryghost-mw-error-handler-5.21.0.tgz +0 -0
  38. package/components/tryghost-mw-session-from-token-5.21.0.tgz +0 -0
  39. package/components/tryghost-mw-update-user-last-seen-5.21.0.tgz +0 -0
  40. package/components/tryghost-mw-vhost-5.21.0.tgz +0 -0
  41. package/components/tryghost-oembed-service-5.21.0.tgz +0 -0
  42. package/components/{tryghost-package-json-5.19.3.tgz → tryghost-package-json-5.21.0.tgz} +0 -0
  43. package/components/tryghost-referrers-5.21.0.tgz +0 -0
  44. package/components/tryghost-security-5.21.0.tgz +0 -0
  45. package/components/tryghost-session-service-5.21.0.tgz +0 -0
  46. package/components/tryghost-settings-path-manager-5.21.0.tgz +0 -0
  47. package/components/tryghost-staff-service-5.21.0.tgz +0 -0
  48. package/components/tryghost-stats-service-5.21.0.tgz +0 -0
  49. package/components/tryghost-tiers-5.21.0.tgz +0 -0
  50. package/components/{tryghost-update-check-service-5.19.3.tgz → tryghost-update-check-service-5.21.0.tgz} +0 -0
  51. package/components/tryghost-verification-trigger-5.21.0.tgz +0 -0
  52. package/components/tryghost-version-notifications-data-service-5.21.0.tgz +0 -0
  53. package/core/boot.js +2 -0
  54. package/core/built/admin/assets/{chunk.143.c035c61595ed02eee886.js → chunk.143.9cddfa7bd1a8b9cf3d4b.js} +7 -7
  55. package/core/built/admin/assets/{chunk.178.998dfbcebcec635146b1.js → chunk.178.6de14cfdb28df721b66e.js} +4 -4
  56. package/core/built/admin/assets/{chunk.613.f1d519ad47e7f9024263.js → chunk.613.695f31829550fb00d43c.js} +352 -421
  57. package/core/built/admin/assets/{chunk.613.f1d519ad47e7f9024263.js.LICENSE.txt → chunk.613.695f31829550fb00d43c.js.LICENSE.txt} +0 -0
  58. package/core/built/admin/assets/{ghost-5ce6f5a730c83c91fc258b12c537ea35.js → ghost-192fee3b46a193df1e65c49a67a7d694.js} +2866 -2707
  59. package/core/built/admin/assets/ghost-9873519a8ad69b5b23284f0a9e050bc6.css +1 -0
  60. package/core/built/admin/assets/ghost-dark-190bdce42b125c3d4be930bd7599b442.css +1 -0
  61. package/core/built/admin/assets/{vendor-5c7d7063620bec13668c4370145cd4b4.js → vendor-26cca1d4d56660dc6e915a12ccc3b330.js} +1079 -1032
  62. package/core/built/admin/index.html +7 -7
  63. package/core/cli/generate-data.js +51 -0
  64. package/core/frontend/helpers/ghost_head.js +1 -1
  65. package/core/server/api/endpoints/links.js +34 -1
  66. package/core/server/api/endpoints/members.js +1 -4
  67. package/core/server/api/endpoints/posts-public.js +1 -1
  68. package/core/server/api/endpoints/posts.js +2 -1
  69. package/core/server/api/endpoints/tiers-public.js +2 -14
  70. package/core/server/api/endpoints/tiers.js +5 -51
  71. package/core/server/api/endpoints/utils/serializers/input/posts.js +21 -1
  72. package/core/server/api/endpoints/utils/serializers/input/settings.js +1 -0
  73. package/core/server/api/endpoints/utils/serializers/input/tiers.js +18 -27
  74. package/core/server/api/endpoints/utils/serializers/output/index.js +4 -0
  75. package/core/server/api/endpoints/utils/serializers/output/links.js +5 -0
  76. package/core/server/api/endpoints/utils/serializers/output/mappers/activity-feed-events.js +89 -15
  77. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +20 -6
  78. package/core/server/api/endpoints/utils/serializers/output/mappers/snippets.js +2 -2
  79. package/core/server/api/endpoints/utils/serializers/output/members.js +6 -5
  80. package/core/server/api/endpoints/utils/serializers/output/tiers.js +15 -55
  81. package/core/server/data/db/backup.js +17 -10
  82. package/core/server/data/importer/importers/data/custom-theme-settings.js +81 -0
  83. package/core/server/data/importer/importers/data/data-importer.js +2 -0
  84. package/core/server/data/migrations/utils/permissions.js +35 -24
  85. package/core/server/data/migrations/versions/5.20/2022-10-18-05-39-drop-nullable-tier-id.js +3 -0
  86. package/core/server/data/migrations/versions/5.20/2022-10-18-10-13-add-ghost-subscription-id-column-to-mscs.js +10 -0
  87. package/core/server/data/migrations/versions/5.20/2022-10-19-11-17-add-link-browse-permissions.js +10 -0
  88. package/core/server/data/migrations/versions/5.20/2022-10-20-02-52-add-link-edit-permissions.js +10 -0
  89. package/core/server/data/migrations/versions/5.21/2022-10-24-07-23-disable-feedback-enabled.js +20 -0
  90. package/core/server/data/migrations/versions/5.21/2022-10-25-12-05-backfill-missed-products-columns.js +35 -0
  91. package/core/server/data/migrations/versions/5.21/2022-10-26-04-49-add-batch-id-members-created-events.js +7 -0
  92. package/core/server/data/migrations/versions/5.21/2022-10-26-04-49-add-batch-id-subscription-created-events.js +7 -0
  93. package/core/server/data/migrations/versions/5.21/2022-10-26-04-50-member-subscription-created-batch-id.js +72 -0
  94. package/core/server/data/migrations/versions/5.21/2022-10-26-09-32-add-feedback-enabled-column-to-emails.js +7 -0
  95. package/core/server/data/migrations/versions/5.21/2022-10-27-09-50-add-member-track-source-setting.js +8 -0
  96. package/core/server/data/schema/commands.js +107 -48
  97. package/core/server/data/schema/default-settings/default-settings.json +8 -0
  98. package/core/server/data/schema/fixtures/fixture-manager.js +16 -14
  99. package/core/server/data/schema/fixtures/fixtures.json +14 -2
  100. package/core/server/data/schema/schema.js +7 -3
  101. package/core/server/models/base/plugins/actions.js +1 -1
  102. package/core/server/models/base/plugins/crud.js +12 -0
  103. package/core/server/models/email-recipient.js +14 -0
  104. package/core/server/models/email.js +2 -5
  105. package/core/server/models/member-click-event.js +37 -0
  106. package/core/server/models/member-created-event.js +23 -0
  107. package/core/server/models/member-paid-subscription-event.js +19 -0
  108. package/core/server/models/member.js +6 -0
  109. package/core/server/models/post.js +33 -2
  110. package/core/server/models/redirect.js +1 -0
  111. package/core/server/models/subscription-created-event.js +7 -0
  112. package/core/server/services/audience-feedback/index.js +2 -0
  113. package/core/server/services/link-redirection/LinkRedirectRepository.js +16 -5
  114. package/core/server/services/link-tracking/PostLinkRepository.js +26 -2
  115. package/core/server/services/link-tracking/index.js +3 -1
  116. package/core/server/services/mega/feedback-buttons.js +87 -16
  117. package/core/server/services/mega/mega.js +1 -0
  118. package/core/server/services/mega/template.js +3 -0
  119. package/core/server/services/member-attribution/index.js +3 -1
  120. package/core/server/services/members/api.js +4 -1
  121. package/core/server/services/members/service.js +8 -1
  122. package/core/server/services/newsletters/index.js +3 -1
  123. package/core/server/services/newsletters/service.js +11 -1
  124. package/core/server/services/tiers/TierRepository.js +116 -0
  125. package/core/server/services/tiers/index.js +1 -0
  126. package/core/server/services/tiers/service.js +32 -0
  127. package/core/server/services/url/UrlGenerator.js +4 -2
  128. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  129. package/core/shared/config/defaults.json +1 -1
  130. package/core/shared/labs.js +7 -8
  131. package/ghost.js +1 -0
  132. package/package.json +117 -113
  133. package/yarn.lock +1187 -1129
  134. package/components/tryghost-adapter-manager-5.19.3.tgz +0 -0
  135. package/components/tryghost-api-version-compatibility-service-5.19.3.tgz +0 -0
  136. package/components/tryghost-audience-feedback-5.19.3.tgz +0 -0
  137. package/components/tryghost-bootstrap-socket-5.19.3.tgz +0 -0
  138. package/components/tryghost-constants-5.19.3.tgz +0 -0
  139. package/components/tryghost-custom-theme-settings-service-5.19.3.tgz +0 -0
  140. package/components/tryghost-domain-events-5.19.3.tgz +0 -0
  141. package/components/tryghost-email-analytics-provider-mailgun-5.19.3.tgz +0 -0
  142. package/components/tryghost-email-analytics-service-5.19.3.tgz +0 -0
  143. package/components/tryghost-email-content-generator-5.19.3.tgz +0 -0
  144. package/components/tryghost-express-dynamic-redirects-5.19.3.tgz +0 -0
  145. package/components/tryghost-extract-api-key-5.19.3.tgz +0 -0
  146. package/components/tryghost-html-to-plaintext-5.19.3.tgz +0 -0
  147. package/components/tryghost-link-redirects-5.19.3.tgz +0 -0
  148. package/components/tryghost-link-replacer-5.19.3.tgz +0 -0
  149. package/components/tryghost-link-tracking-5.19.3.tgz +0 -0
  150. package/components/tryghost-magic-link-5.19.3.tgz +0 -0
  151. package/components/tryghost-mailgun-client-5.19.3.tgz +0 -0
  152. package/components/tryghost-member-analytics-service-5.19.3.tgz +0 -0
  153. package/components/tryghost-member-attribution-5.19.3.tgz +0 -0
  154. package/components/tryghost-member-events-5.19.3.tgz +0 -0
  155. package/components/tryghost-members-analytics-ingress-5.19.3.tgz +0 -0
  156. package/components/tryghost-members-api-5.19.3.tgz +0 -0
  157. package/components/tryghost-members-csv-5.19.3.tgz +0 -0
  158. package/components/tryghost-members-events-service-5.19.3.tgz +0 -0
  159. package/components/tryghost-members-importer-5.19.3.tgz +0 -0
  160. package/components/tryghost-members-offers-5.19.3.tgz +0 -0
  161. package/components/tryghost-members-payments-5.19.3.tgz +0 -0
  162. package/components/tryghost-members-stripe-service-5.19.3.tgz +0 -0
  163. package/components/tryghost-minifier-5.19.3.tgz +0 -0
  164. package/components/tryghost-mw-api-version-mismatch-5.19.3.tgz +0 -0
  165. package/components/tryghost-mw-cache-control-5.19.3.tgz +0 -0
  166. package/components/tryghost-mw-error-handler-5.19.3.tgz +0 -0
  167. package/components/tryghost-mw-session-from-token-5.19.3.tgz +0 -0
  168. package/components/tryghost-mw-update-user-last-seen-5.19.3.tgz +0 -0
  169. package/components/tryghost-mw-vhost-5.19.3.tgz +0 -0
  170. package/components/tryghost-oembed-service-5.19.3.tgz +0 -0
  171. package/components/tryghost-referrers-5.19.3.tgz +0 -0
  172. package/components/tryghost-security-5.19.3.tgz +0 -0
  173. package/components/tryghost-session-service-5.19.3.tgz +0 -0
  174. package/components/tryghost-settings-path-manager-5.19.3.tgz +0 -0
  175. package/components/tryghost-staff-service-5.19.3.tgz +0 -0
  176. package/components/tryghost-stats-service-5.19.3.tgz +0 -0
  177. package/components/tryghost-verification-trigger-5.19.3.tgz +0 -0
  178. package/components/tryghost-version-notifications-data-service-5.19.3.tgz +0 -0
  179. package/core/built/admin/assets/ghost-982146a4ada3a5af1981d1919ae01d08.css +0 -1
  180. package/core/built/admin/assets/ghost-dark-41929e4857de411a23597a9de49a4e4f.css +0 -1
@@ -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.21%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-9873519a8ad69b5b23284f0a9e050bc6.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-26cca1d4d56660dc6e915a12ccc3b330.js"></script>
60
+ <script src="assets/chunk.613.695f31829550fb00d43c.js"></script>
61
+ <script src="assets/chunk.143.9cddfa7bd1a8b9cf3d4b.js"></script>
62
+ <script src="assets/ghost-192fee3b46a193df1e65c49a67a7d694.js"></script>
63
63
  </body>
64
64
  </html>
@@ -0,0 +1,51 @@
1
+ const Command = require('./command');
2
+ const DataGenerator = require('@tryghost/data-generator');
3
+
4
+ module.exports = class REPL extends Command {
5
+ setup() {
6
+ this.help('Generates random data to populate the database for development & testing');
7
+ this.argument('--use-base-data', {type: 'boolean', defaultValue: false, desc: 'Only generate data outside of a defined base data set'});
8
+ }
9
+
10
+ initializeContext(context) {
11
+ const models = require('../server/models');
12
+ const knex = require('../server/data/db/connection');
13
+
14
+ models.init();
15
+
16
+ context.models = models;
17
+ context.m = models;
18
+ context.knex = knex;
19
+ context.k = knex;
20
+ }
21
+
22
+ permittedEnvironments() {
23
+ return ['development', 'local', 'staging', 'production'];
24
+ }
25
+
26
+ async handle(argv = {}) {
27
+ const knex = require('../server/data/db/connection');
28
+ const {tables: schema} = require('../server/data/schema/index');
29
+ const dataGenerator = new DataGenerator({
30
+ useBaseData: argv['use-base-data'],
31
+ knex,
32
+ schema,
33
+ logger: {
34
+ log: this.log,
35
+ ok: this.ok,
36
+ info: this.info,
37
+ warn: this.warn,
38
+ error: this.error,
39
+ fatal: this.fatal,
40
+ debug: this.debug
41
+ },
42
+ modelQuantities: {}
43
+ });
44
+ try {
45
+ await dataGenerator.importData();
46
+ } catch (error) {
47
+ this.fatal('Failed while generating data: ', error);
48
+ }
49
+ knex.destroy();
50
+ }
51
+ };
@@ -234,7 +234,7 @@ module.exports = async function ghost_head(options) { // eslint-disable-line cam
234
234
  head.push(`<script defer src="${getAssetUrl('public/comment-counts.min.js')}" data-ghost-comments-counts-api="${urlUtils.getSiteUrl(true)}members/api/comments/counts/"></script>`);
235
235
  }
236
236
 
237
- if (settingsCache.get('members_enabled')) {
237
+ if (settingsCache.get('members_enabled') && settingsCache.get('members_track_sources')) {
238
238
  head.push(`<script defer src="${getAssetUrl('public/member-attribution.min.js')}"></script>`);
239
239
  }
240
240
 
@@ -1,4 +1,5 @@
1
1
  const linkTrackingService = require('../../services/link-tracking');
2
+ const INVALIDATE_ALL_REDIRECTS = '/r/*';
2
3
 
3
4
  module.exports = {
4
5
  docName: 'links',
@@ -6,7 +7,7 @@ module.exports = {
6
7
  options: [
7
8
  'filter'
8
9
  ],
9
- permissions: false,
10
+ permissions: true,
10
11
  async query(frame) {
11
12
  const links = await linkTrackingService.service.getLinks(frame.options);
12
13
 
@@ -21,5 +22,37 @@ module.exports = {
21
22
  }
22
23
  };
23
24
  }
25
+ },
26
+ bulkEdit: {
27
+ statusCode: 200,
28
+ headers: {
29
+ cacheInvalidate: INVALIDATE_ALL_REDIRECTS
30
+ },
31
+ options: [
32
+ 'filter'
33
+ ],
34
+ data: [
35
+ 'action',
36
+ 'meta'
37
+ ],
38
+ validation: {
39
+ data: {
40
+ action: {
41
+ required: true,
42
+ values: ['updateLink']
43
+ }
44
+ },
45
+ options: {
46
+ filter: {
47
+ required: true
48
+ }
49
+ }
50
+ },
51
+ permissions: {
52
+ method: 'edit'
53
+ },
54
+ async query(frame) {
55
+ return await linkTrackingService.service.bulkEdit(frame.data.bulk, frame.options);
56
+ }
24
57
  }
25
58
  };
@@ -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
 
@@ -1,8 +1,4 @@
1
- // NOTE: We must not cache references to membersService.api
2
- // as it is a getter and may change during runtime.
3
- const membersService = require('../../services/members');
4
-
5
- const allowedIncludes = ['monthly_price', 'yearly_price', 'benefits'];
1
+ const tiersService = require('../../services/tiers');
6
2
 
7
3
  module.exports = {
8
4
  docName: 'tiers',
@@ -11,22 +7,14 @@ module.exports = {
11
7
  options: [
12
8
  'limit',
13
9
  'fields',
14
- 'include',
15
10
  'filter',
16
11
  'order',
17
12
  'debug',
18
13
  'page'
19
14
  ],
20
15
  permissions: true,
21
- validation: {
22
- options: {
23
- include: {
24
- values: allowedIncludes
25
- }
26
- }
27
- },
28
16
  async query(frame) {
29
- const page = await membersService.api.productRepository.list(frame.options);
17
+ const page = await tiersService.api.browse(frame.options);
30
18
 
31
19
  return page;
32
20
  }
@@ -1,13 +1,4 @@
1
- const errors = require('@tryghost/errors');
2
- const membersService = require('../../services/members');
3
-
4
- const tpl = require('@tryghost/tpl');
5
-
6
- const allowedIncludes = ['monthly_price', 'yearly_price', 'benefits'];
7
-
8
- const messages = {
9
- productNotFound: 'Tier not found.'
10
- };
1
+ const tiersService = require('../../services/tiers');
11
2
 
12
3
  module.exports = {
13
4
  docName: 'tiers',
@@ -16,7 +7,6 @@ module.exports = {
16
7
  options: [
17
8
  'limit',
18
9
  'fields',
19
- 'include',
20
10
  'filter',
21
11
  'order',
22
12
  'debug',
@@ -25,48 +15,21 @@ module.exports = {
25
15
  permissions: {
26
16
  docName: 'products'
27
17
  },
28
- validation: {
29
- options: {
30
- include: {
31
- values: allowedIncludes
32
- }
33
- }
34
- },
35
18
  async query(frame) {
36
- const page = await membersService.api.productRepository.list(frame.options);
37
-
19
+ const page = await tiersService.api.browse(frame.options);
38
20
  return page;
39
21
  }
40
22
  },
41
23
 
42
24
  read: {
43
- options: [
44
- 'include'
45
- ],
46
- headers: {},
47
25
  data: [
48
26
  'id'
49
27
  ],
50
- validation: {
51
- options: {
52
- include: {
53
- values: allowedIncludes
54
- }
55
- }
56
- },
57
28
  permissions: {
58
29
  docName: 'products'
59
30
  },
60
31
  async query(frame) {
61
- const model = await membersService.api.productRepository.get(frame.data, frame.options);
62
-
63
- if (!model) {
64
- throw new errors.NotFoundError({
65
- message: tpl(messages.productNotFound)
66
- });
67
- }
68
-
69
- return model;
32
+ return await tiersService.api.read(frame.data.id);
70
33
  }
71
34
  },
72
35
 
@@ -84,11 +47,7 @@ module.exports = {
84
47
  docName: 'products'
85
48
  },
86
49
  async query(frame) {
87
- const model = await membersService.api.productRepository.create(
88
- frame.data,
89
- frame.options
90
- );
91
- return model;
50
+ return await tiersService.api.add(frame.data);
92
51
  }
93
52
  },
94
53
 
@@ -111,12 +70,7 @@ module.exports = {
111
70
  docName: 'products'
112
71
  },
113
72
  async query(frame) {
114
- const model = await membersService.api.productRepository.update(
115
- frame.data,
116
- frame.options
117
- );
118
-
119
- return model;
73
+ return await tiersService.api.edit(frame.options.id, frame.data);
120
74
  }
121
75
  }
122
76
  };
@@ -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', 'count.negative_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)) {
@@ -48,6 +48,7 @@ const EDITABLE_SETTINGS = [
48
48
  'mailgun_base_url',
49
49
  'email_track_opens',
50
50
  'email_track_clicks',
51
+ 'members_track_sources',
51
52
  'amp',
52
53
  'amp_gtag_id',
53
54
  'slack_url',
@@ -1,5 +1,4 @@
1
1
  const localUtils = require('../../index');
2
- const labs = require('../../../../../../shared/labs');
3
2
 
4
3
  const forceActiveFilter = (frame) => {
5
4
  if (frame.options.filter) {
@@ -10,39 +9,31 @@ const forceActiveFilter = (frame) => {
10
9
  };
11
10
 
12
11
  function convertTierInput(input) {
13
- const converted = {
14
- id: input.id,
15
- name: input.name,
16
- description: input.description,
17
- slug: input.slug,
18
- active: input.active,
19
- type: input.type,
20
- welcome_page_url: input.welcome_page_url,
21
- created_at: input.created_at,
22
- updated_at: input.updated_at,
23
- visibility: input.visibility
24
- };
12
+ const converted = Object.assign({}, input);
25
13
 
26
- if (labs.isSet('freeTrial')) {
27
- converted.trial_days = input.trial_days || 0;
14
+ if (Reflect.has(converted, 'active')) {
15
+ converted.status = converted.active ? 'active' : 'archived';
16
+ delete converted.active;
28
17
  }
29
18
 
30
- if (input.monthly_price && input.currency) {
31
- converted.monthly_price = {
32
- amount: input.monthly_price,
33
- currency: input.currency
34
- };
19
+ if (Reflect.has(converted, 'welcome_page_url')) {
20
+ converted.welcomePageURL = converted.welcome_page_url;
21
+ delete converted.welcome_page_url;
35
22
  }
36
23
 
37
- if (input.yearly_price && input.currency) {
38
- converted.yearly_price = {
39
- amount: input.yearly_price,
40
- currency: input.currency
41
- };
24
+ if (Reflect.has(converted, 'trial_days')) {
25
+ converted.trialDays = converted.trial_days;
26
+ delete converted.trial_days;
42
27
  }
43
28
 
44
- if (input.benefits) {
45
- converted.benefits = input.benefits.map(name => ({name}));
29
+ if (Reflect.has(converted, 'monthly_price')) {
30
+ converted.monthlyPrice = converted.monthly_price;
31
+ delete converted.monthly_price;
32
+ }
33
+
34
+ if (Reflect.has(converted, 'yearly_price')) {
35
+ converted.yearlyPrice = converted.yearly_price;
36
+ delete converted.yearly_price;
46
37
  }
47
38
 
48
39
  return converted;
@@ -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
 
@@ -53,6 +53,67 @@ const clickEventMapper = (json, frame) => {
53
53
  response.created_at = data.created_at;
54
54
  }
55
55
 
56
+ if (data.id) {
57
+ response.id = data.id;
58
+ }
59
+
60
+ return {
61
+ ...json,
62
+ data: response
63
+ };
64
+ };
65
+
66
+ const aggregatedClickEventMapper = (json) => {
67
+ const data = json.data;
68
+ const response = {};
69
+
70
+ if (data.member) {
71
+ response.member = _.pick(data.member, memberFields);
72
+ } else {
73
+ response.member = null;
74
+ }
75
+
76
+ if (data.created_at) {
77
+ response.created_at = data.created_at;
78
+ }
79
+
80
+ if (data.id) {
81
+ response.id = data.id;
82
+ }
83
+
84
+ response.count = {
85
+ clicks: data.count?.clicks ?? 0
86
+ };
87
+
88
+ return {
89
+ ...json,
90
+ data: response
91
+ };
92
+ };
93
+
94
+ const feedbackEventMapper = (json, frame) => {
95
+ const feedbackFields = [
96
+ 'id',
97
+ 'score',
98
+ 'created_at'
99
+ ];
100
+
101
+ const data = json.data;
102
+ const response = _.pick(data, feedbackFields);
103
+
104
+ if (data.post) {
105
+ url.forPost(data.post.id, data.post, frame);
106
+ response.post = _.pick(data.post, postFields);
107
+ } else {
108
+ response.post = null;
109
+ }
110
+
111
+ if (data.member) {
112
+ response.member = _.pick(data.member, memberFields);
113
+ } else {
114
+ response.member = null;
115
+ }
116
+
56
117
  return {
57
118
  ...json,
58
119
  data: response
@@ -82,9 +143,22 @@ const activityFeedMapper = (event, frame) => {
82
143
  if (event.type === 'click_event') {
83
144
  return clickEventMapper(event, frame);
84
145
  }
146
+ if (event.type === 'aggregated_click_event') {
147
+ return aggregatedClickEventMapper(event, frame);
148
+ }
149
+ if (event.type === 'feedback_event') {
150
+ return feedbackEventMapper(event, frame);
151
+ }
85
152
  if (event.data?.attribution) {
86
153
  event.data.attribution = serializeAttribution(event.data.attribution);
87
154
  }
155
+ // TODO: add dedicated mappers for other event types
156
+ if (event.data?.batch_id) {
157
+ delete event.data.batch_id;
158
+ }
159
+ if (event.data?.subscriptionCreatedEvent) {
160
+ delete event.data.subscriptionCreatedEvent;
161
+ }
88
162
  return event;
89
163
  };
90
164
 
@@ -111,21 +111,35 @@ module.exports = async (model, frame, options = {}) => {
111
111
 
112
112
  if (jsonModel.email && jsonModel.count) {
113
113
  jsonModel.email.opened_count = Math.min(
114
- Math.max(
115
- jsonModel.email.opened_count || 0,
116
- jsonModel.count.clicks || 0
117
- ),
114
+ jsonModel.email.opened_count || 0,
118
115
  jsonModel.email.email_count
119
116
  );
120
117
  }
121
118
 
122
- if (jsonModel.count && !jsonModel.count.sentiment) {
123
- jsonModel.count.sentiment = 0;
119
+ // 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
120
+ // This part moves count.sentiment to just 'sentiment' when it has been loaded
121
+ if (frame.options.withRelated && frame.options.withRelated.includes('count.sentiment')) {
122
+ if (!jsonModel.count) {
123
+ jsonModel.sentiment = 0;
124
+ } else {
125
+ jsonModel.sentiment = jsonModel.count.sentiment ?? 0;
126
+
127
+ // Delete it from the original location
128
+ delete jsonModel.count.sentiment;
129
+
130
+ if (Object.keys(jsonModel.count).length === 0) {
131
+ delete jsonModel.count;
132
+ }
133
+ }
124
134
  }
125
135
 
126
136
  if (jsonModel.count && !jsonModel.count.positive_feedback) {
127
137
  jsonModel.count.positive_feedback = 0;
128
138
  }
129
139
 
140
+ if (jsonModel.count && !jsonModel.count.negative_feedback) {
141
+ jsonModel.count.negative_feedback = 0;
142
+ }
143
+
130
144
  return jsonModel;
131
145
  };
@@ -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