ghost 5.9.4 → 5.11.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 (178) hide show
  1. package/components/tryghost-adapter-manager-5.11.0.tgz +0 -0
  2. package/components/tryghost-api-framework-5.11.0.tgz +0 -0
  3. package/components/{tryghost-api-version-compatibility-service-0.0.0.tgz → tryghost-api-version-compatibility-service-5.11.0.tgz} +0 -0
  4. package/components/tryghost-bootstrap-socket-5.11.0.tgz +0 -0
  5. package/components/tryghost-constants-5.11.0.tgz +0 -0
  6. package/components/tryghost-custom-theme-settings-service-5.11.0.tgz +0 -0
  7. package/components/tryghost-domain-events-5.11.0.tgz +0 -0
  8. package/components/tryghost-email-analytics-provider-mailgun-5.11.0.tgz +0 -0
  9. package/components/tryghost-email-analytics-service-5.11.0.tgz +0 -0
  10. package/components/tryghost-email-content-generator-5.11.0.tgz +0 -0
  11. package/components/tryghost-express-dynamic-redirects-5.11.0.tgz +0 -0
  12. package/components/tryghost-extract-api-key-5.11.0.tgz +0 -0
  13. package/components/tryghost-html-to-plaintext-5.11.0.tgz +0 -0
  14. package/components/tryghost-job-manager-5.11.0.tgz +0 -0
  15. package/components/tryghost-magic-link-5.11.0.tgz +0 -0
  16. package/components/tryghost-mailgun-client-5.11.0.tgz +0 -0
  17. package/components/tryghost-member-analytics-service-5.11.0.tgz +0 -0
  18. package/components/tryghost-member-attribution-5.11.0.tgz +0 -0
  19. package/components/tryghost-member-events-5.11.0.tgz +0 -0
  20. package/components/tryghost-members-analytics-ingress-5.11.0.tgz +0 -0
  21. package/components/tryghost-members-api-5.11.0.tgz +0 -0
  22. package/components/{tryghost-members-csv-0.0.0.tgz → tryghost-members-csv-5.11.0.tgz} +0 -0
  23. package/components/tryghost-members-events-service-5.11.0.tgz +0 -0
  24. package/components/tryghost-members-importer-5.11.0.tgz +0 -0
  25. package/components/tryghost-members-offers-5.11.0.tgz +0 -0
  26. package/components/tryghost-members-payments-5.11.0.tgz +0 -0
  27. package/components/tryghost-members-ssr-5.11.0.tgz +0 -0
  28. package/components/tryghost-members-stripe-service-5.11.0.tgz +0 -0
  29. package/components/tryghost-minifier-5.11.0.tgz +0 -0
  30. package/components/tryghost-mw-api-version-mismatch-5.11.0.tgz +0 -0
  31. package/components/tryghost-mw-cache-control-5.11.0.tgz +0 -0
  32. package/components/tryghost-mw-error-handler-5.11.0.tgz +0 -0
  33. package/components/tryghost-mw-session-from-token-5.11.0.tgz +0 -0
  34. package/components/tryghost-mw-update-user-last-seen-5.11.0.tgz +0 -0
  35. package/components/tryghost-mw-vhost-5.11.0.tgz +0 -0
  36. package/components/tryghost-oembed-service-5.11.0.tgz +0 -0
  37. package/components/tryghost-package-json-5.11.0.tgz +0 -0
  38. package/components/tryghost-security-5.11.0.tgz +0 -0
  39. package/components/tryghost-session-service-5.11.0.tgz +0 -0
  40. package/components/tryghost-settings-path-manager-5.11.0.tgz +0 -0
  41. package/components/tryghost-update-check-service-5.11.0.tgz +0 -0
  42. package/components/tryghost-verification-trigger-5.11.0.tgz +0 -0
  43. package/components/tryghost-version-notifications-data-service-5.11.0.tgz +0 -0
  44. package/content/themes/casper/assets/built/screen.css +1 -1
  45. package/content/themes/casper/assets/built/screen.css.map +1 -1
  46. package/content/themes/casper/assets/css/screen.css +8 -5
  47. package/content/themes/casper/package.json +1 -1
  48. package/core/boot.js +2 -0
  49. package/core/bridge.js +2 -0
  50. package/core/built/admin/assets/chunk.143.14589cc066b8120b73e3.js +49 -0
  51. package/core/built/admin/assets/{chunk.174.eec7f6398cef4c3e2485.js → chunk.174.ae492405065373dbe102.js} +31 -29
  52. package/core/built/admin/assets/{chunk.178.506264293194a4922091.js → chunk.178.131e85a10d2031148425.js} +4 -4
  53. package/core/built/admin/assets/{chunk.351.73f27952f867334a8228.js → chunk.579.65e09dd89eec70d059a0.js} +23 -28
  54. package/core/built/admin/assets/{chunk.351.73f27952f867334a8228.js.LICENSE.txt → chunk.579.65e09dd89eec70d059a0.js.LICENSE.txt} +0 -0
  55. package/core/built/admin/assets/ghost-1b0d7c731511bb738ec457d2932c43c0.css +1 -0
  56. package/core/built/admin/assets/{ghost-b441c9cfa2e31453e86460e50ae7e378.js → ghost-40f5bd12d121c54bbc39e7939e78244f.js} +827 -611
  57. package/core/built/admin/assets/ghost-dark-7b2825a050b0382630180f48aa78ea5d.css +1 -0
  58. package/core/built/admin/assets/icons/calendar-stroke.svg +1 -0
  59. package/core/built/admin/assets/icons/ghost-orb-pink.svg +10 -0
  60. package/core/built/admin/assets/icons/pen-stroke.svg +1 -0
  61. package/core/built/admin/assets/img/logos/orb-pink-3-a2c52eb9fda9f2401ea706c3f24976ff.png +0 -0
  62. package/core/built/admin/assets/{vendor-516c9e43b4aeb92079dc1ab92c9ce492.js → vendor-741dc0e4078e044a0c9bfaad104de8b3.js} +85 -78
  63. package/core/built/admin/index.html +6 -6
  64. package/core/frontend/helpers/ghost_head.js +4 -0
  65. package/core/frontend/helpers/search.js +42 -0
  66. package/core/frontend/services/member-attribution-assets/index.js +4 -0
  67. package/core/frontend/services/member-attribution-assets/service.js +83 -0
  68. package/core/frontend/src/member-attribution/.eslintrc +10 -0
  69. package/core/frontend/src/member-attribution/member-attribution.js +90 -0
  70. package/core/frontend/web/site.js +3 -0
  71. package/core/server/adapters/cache/ImageSizesCacheSyncInMemory.js +7 -0
  72. package/core/server/adapters/cache/SettingsCacheSyncInMemory.js +7 -0
  73. package/core/server/api/endpoints/comments-members.js +10 -7
  74. package/core/server/api/endpoints/invites.js +1 -9
  75. package/core/server/api/endpoints/labels.js +1 -7
  76. package/core/server/api/endpoints/members.js +3 -13
  77. package/core/server/api/endpoints/offers.js +2 -2
  78. package/core/server/api/endpoints/pages.js +2 -10
  79. package/core/server/api/endpoints/posts.js +11 -10
  80. package/core/server/api/endpoints/snippets.js +1 -9
  81. package/core/server/api/endpoints/tags.js +1 -7
  82. package/core/server/api/endpoints/utils/serializers/input/pages.js +1 -1
  83. package/core/server/api/endpoints/utils/serializers/input/posts.js +1 -1
  84. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +5 -0
  85. package/core/server/api/endpoints/utils/serializers/output/members.js +2 -1
  86. package/core/server/api/endpoints/utils/serializers/output/site.js +1 -0
  87. package/core/server/api/endpoints/utils/serializers/output/utils/clean.js +6 -7
  88. package/core/server/api/endpoints/webhooks.js +2 -19
  89. package/core/server/data/exporter/table-lists.js +2 -0
  90. package/core/server/data/migrations/versions/5.10/2022-08-15-05-34-add-expiry-at-column-to-members-products.js +6 -0
  91. package/core/server/data/migrations/versions/5.10/2022-08-16-14-25-add-member-created-events-table.js +11 -0
  92. package/core/server/data/migrations/versions/5.10/2022-08-16-14-25-add-subscription-created-events-table.js +11 -0
  93. package/core/server/data/migrations/versions/5.10/2022-08-19-14-15-fix-comments-deletion-strategy.js +45 -0
  94. package/core/server/data/migrations/versions/5.11/2022-08-22-11-03-add-member-alert-settings-columns-to-users.js +21 -0
  95. package/core/server/data/migrations/versions/5.11/2022-08-23-13-41-backfill-members-created-events.js +32 -0
  96. package/core/server/data/migrations/versions/5.11/2022-08-23-13-59-fix-page-resource-type.js +22 -0
  97. package/core/server/data/schema/fixtures/fixtures.json +3 -0
  98. package/core/server/data/schema/schema.js +24 -2
  99. package/core/server/lib/image/cached-image-size-from-url.js +52 -28
  100. package/core/server/lib/image/gravatar.js +8 -7
  101. package/core/server/lib/image/image-size.js +60 -56
  102. package/core/server/lib/image/image-utils.js +5 -2
  103. package/core/server/lib/image/index.js +14 -1
  104. package/core/server/models/action.js +0 -10
  105. package/core/server/models/api-key.js +3 -18
  106. package/core/server/models/base/plugins/actions.js +55 -0
  107. package/core/server/models/integration.js +3 -0
  108. package/core/server/models/label.js +3 -18
  109. package/core/server/models/member-created-event.js +26 -0
  110. package/core/server/models/member.js +54 -4
  111. package/core/server/models/offer.js +3 -0
  112. package/core/server/models/post.js +25 -18
  113. package/core/server/models/product.js +3 -0
  114. package/core/server/models/settings.js +4 -0
  115. package/core/server/models/subscription-created-event.js +30 -0
  116. package/core/server/models/tag.js +3 -18
  117. package/core/server/models/user.js +7 -19
  118. package/core/server/models/webhook.js +3 -0
  119. package/core/server/services/auth/api-key/admin.js +0 -3
  120. package/core/server/services/auth/passwordreset.js +0 -3
  121. package/core/server/services/comments/emails.js +3 -3
  122. package/core/server/services/explore/service.js +8 -6
  123. package/core/server/services/member-attribution/index.js +52 -0
  124. package/core/server/services/members/api.js +3 -1
  125. package/core/server/services/members/jobs/clean-expired-comped.js +105 -0
  126. package/core/server/services/members/jobs/index.js +27 -0
  127. package/core/server/services/members/service.js +14 -8
  128. package/core/server/services/public-config/site.js +1 -0
  129. package/core/server/services/route-settings/default-settings-manager.js +19 -17
  130. package/core/server/services/settings/settings-service.js +1 -1
  131. package/core/server/services/webhooks/trigger.js +14 -5
  132. package/core/shared/config/defaults.json +8 -3
  133. package/core/shared/labs.js +5 -2
  134. package/package.json +84 -83
  135. package/yarn.lock +440 -615
  136. package/components/tryghost-adapter-manager-0.0.0.tgz +0 -0
  137. package/components/tryghost-api-framework-0.0.0.tgz +0 -0
  138. package/components/tryghost-bootstrap-socket-0.0.0.tgz +0 -0
  139. package/components/tryghost-constants-0.0.0.tgz +0 -0
  140. package/components/tryghost-custom-theme-settings-service-0.0.0.tgz +0 -0
  141. package/components/tryghost-domain-events-0.0.0.tgz +0 -0
  142. package/components/tryghost-email-analytics-provider-mailgun-0.0.0.tgz +0 -0
  143. package/components/tryghost-email-analytics-service-0.0.0.tgz +0 -0
  144. package/components/tryghost-email-content-generator-0.0.0.tgz +0 -0
  145. package/components/tryghost-express-dynamic-redirects-0.0.0.tgz +0 -0
  146. package/components/tryghost-extract-api-key-0.0.0.tgz +0 -0
  147. package/components/tryghost-html-to-plaintext-0.0.0.tgz +0 -0
  148. package/components/tryghost-job-manager-0.0.0.tgz +0 -0
  149. package/components/tryghost-magic-link-0.0.0.tgz +0 -0
  150. package/components/tryghost-mailgun-client-0.0.0.tgz +0 -0
  151. package/components/tryghost-member-analytics-service-0.0.0.tgz +0 -0
  152. package/components/tryghost-member-events-0.0.0.tgz +0 -0
  153. package/components/tryghost-members-analytics-ingress-0.0.0.tgz +0 -0
  154. package/components/tryghost-members-api-0.0.0.tgz +0 -0
  155. package/components/tryghost-members-events-service-0.0.0.tgz +0 -0
  156. package/components/tryghost-members-importer-0.0.0.tgz +0 -0
  157. package/components/tryghost-members-offers-0.0.0.tgz +0 -0
  158. package/components/tryghost-members-payments-0.0.0.tgz +0 -0
  159. package/components/tryghost-members-ssr-0.0.0.tgz +0 -0
  160. package/components/tryghost-members-stripe-service-0.0.0.tgz +0 -0
  161. package/components/tryghost-minifier-0.0.0.tgz +0 -0
  162. package/components/tryghost-mw-api-version-mismatch-0.0.0.tgz +0 -0
  163. package/components/tryghost-mw-cache-control-0.0.0.tgz +0 -0
  164. package/components/tryghost-mw-error-handler-0.0.0.tgz +0 -0
  165. package/components/tryghost-mw-session-from-token-0.0.0.tgz +0 -0
  166. package/components/tryghost-mw-update-user-last-seen-0.0.0.tgz +0 -0
  167. package/components/tryghost-mw-vhost-0.0.0.tgz +0 -0
  168. package/components/tryghost-oembed-service-0.0.0.tgz +0 -0
  169. package/components/tryghost-package-json-0.0.0.tgz +0 -0
  170. package/components/tryghost-security-0.0.0.tgz +0 -0
  171. package/components/tryghost-session-service-0.0.0.tgz +0 -0
  172. package/components/tryghost-settings-path-manager-0.0.0.tgz +0 -0
  173. package/components/tryghost-update-check-service-0.0.0.tgz +0 -0
  174. package/components/tryghost-verification-trigger-0.0.0.tgz +0 -0
  175. package/components/tryghost-version-notifications-data-service-0.0.0.tgz +0 -0
  176. package/core/built/admin/assets/chunk.143.1c158e8ef19f10e5439c.js +0 -41
  177. package/core/built/admin/assets/ghost-dark-4080c8f100997d4b8947f5da0e7946a1.css +0 -1
  178. package/core/built/admin/assets/ghost-facfdf4a7d9759c5b681340805f21fd8.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.9%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%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.11%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%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-bc9d2c9e5c8a33f0c92e81189d48e04c.css">
40
- <link integrity="" rel="stylesheet" href="assets/ghost-facfdf4a7d9759c5b681340805f21fd8.css" title="light">
40
+ <link integrity="" rel="stylesheet" href="assets/ghost-1b0d7c731511bb738ec457d2932c43c0.css" title="light">
41
41
 
42
42
 
43
43
  </head>
@@ -53,9 +53,9 @@
53
53
 
54
54
  <div id="ember-basic-dropdown-wormhole"></div>
55
55
 
56
- <script src="assets/vendor-516c9e43b4aeb92079dc1ab92c9ce492.js"></script>
57
- <script src="assets/chunk.351.73f27952f867334a8228.js"></script>
58
- <script src="assets/chunk.143.1c158e8ef19f10e5439c.js"></script>
59
- <script src="assets/ghost-b441c9cfa2e31453e86460e50ae7e378.js"></script>
56
+ <script src="assets/vendor-741dc0e4078e044a0c9bfaad104de8b3.js"></script>
57
+ <script src="assets/chunk.579.65e09dd89eec70d059a0.js"></script>
58
+ <script src="assets/chunk.143.14589cc066b8120b73e3.js"></script>
59
+ <script src="assets/ghost-40f5bd12d121c54bbc39e7939e78244f.js"></script>
60
60
  </body>
61
61
  </html>
@@ -233,6 +233,10 @@ module.exports = async function ghost_head(options) { // eslint-disable-line cam
233
233
  head.push(`<script defer src="${getAssetUrl('public/comment-counts.min.js')}" data-ghost-comments-counts-api="${urlUtils.getSiteUrl(true)}members/api/comments/counts/"></script>`);
234
234
  }
235
235
 
236
+ if (labs.isSet('memberAttribution')) {
237
+ head.push(`<script defer src="${getAssetUrl('public/member-attribution.min.js')}"></script>`);
238
+ }
239
+
236
240
  if (!_.isEmpty(globalCodeinjection)) {
237
241
  head.push(globalCodeinjection);
238
242
  }
@@ -0,0 +1,42 @@
1
+ // # search helper
2
+
3
+ const {SafeString} = require('../services/handlebars');
4
+ const {labs} = require('../services/proxy');
5
+
6
+ function search() {
7
+ const svg = `<style>.gh-search-icon {
8
+ display: inline-flex;
9
+ justify-content: center;
10
+ align-items: center;
11
+ width: 32px;
12
+ height: 32px;
13
+ padding: 0;
14
+ border: 0;
15
+ color: inherit;
16
+ background-color: transparent;
17
+ cursor: pointer;
18
+ outline: none;
19
+ }</style>
20
+ <button class="gh-search-icon" aria-label="search" data-ghost-search>
21
+ <svg width="20" height="20" fill="none" viewBox="0 0 24 24">
22
+ <path d="M14.949 14.949a1 1 0 0 1 1.414 0l6.344 6.344a1 1 0 0 1-1.414 1.414l-6.344-6.344a1 1 0 0 1 0-1.414Z"
23
+ fill="currentColor"/>
24
+ <path d="M10 3a7 7 0 1 0 0 14 7 7 0 0 0 0-14Zm-9 7a9 9 0 1 1 18 0 9 9 0 0 1-18 0Z" fill="currentColor"/>
25
+ </svg>
26
+ </button>`;
27
+
28
+ return new SafeString(svg);
29
+ }
30
+
31
+ module.exports = function searchLabsWrapper() {
32
+ let self = this;
33
+ let args = arguments;
34
+
35
+ return labs.enabledHelper({
36
+ flagKey: 'searchHelper',
37
+ flagName: 'Search helper',
38
+ helperName: 'search'
39
+ }, () => {
40
+ return search.apply(self, args); // eslint-disable-line camelcase
41
+ });
42
+ };
@@ -0,0 +1,4 @@
1
+ const MemberAttributionAssetsService = require('./service');
2
+ const memberAttributionAssets = new MemberAttributionAssetsService();
3
+
4
+ module.exports = memberAttributionAssets;
@@ -0,0 +1,83 @@
1
+ // const debug = require('@tryghost/debug')('comments-counts-assets');
2
+ const Minifier = require('@tryghost/minifier');
3
+ const path = require('path');
4
+ const fs = require('fs').promises;
5
+ const logging = require('@tryghost/logging');
6
+ const config = require('../../../shared/config');
7
+
8
+ class MemberAttributionAssetsService {
9
+ constructor(options = {}) {
10
+ /** @private */
11
+ this.src = options.src || path.join(config.get('paths').assetSrc, 'member-attribution');
12
+ /** @private */
13
+ this.dest = options.dest || config.getContentPath('public');
14
+ /** @private */
15
+ this.minifier = new Minifier({src: this.src, dest: this.dest});
16
+ }
17
+
18
+ /**
19
+ * @private
20
+ */
21
+ generateGlobs() {
22
+ return {
23
+ 'member-attribution.min.js': '*.js'
24
+ };
25
+ }
26
+
27
+ /**
28
+ * @private
29
+ */
30
+ generateReplacements() {
31
+ return {};
32
+ }
33
+
34
+ /**
35
+ * @private
36
+ * @returns {Promise<void>}
37
+ */
38
+ async minify(globs, options) {
39
+ try {
40
+ await this.minifier.minify(globs, options);
41
+ } catch (error) {
42
+ if (error.code === 'EACCES') {
43
+ logging.error('Ghost was not able to write member-attribution asset files due to permissions.');
44
+ return;
45
+ }
46
+
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * @private
53
+ * @returns {Promise<void>}
54
+ */
55
+ async clearFiles() {
56
+ const rmFile = async (name) => {
57
+ await fs.unlink(path.join(this.dest, name));
58
+ };
59
+
60
+ const promises = [];
61
+ for (const key of Object.keys(this.generateGlobs())) {
62
+ // @deprecated switch this to use fs.rm when we drop support for Node v12
63
+ promises.push(rmFile(key));
64
+ }
65
+
66
+ // We don't care if removing these files fails as it's valid for them to not exist
67
+ await Promise.allSettled(promises);
68
+ }
69
+
70
+ /**
71
+ * Minify, move into the destination directory, and clear existing asset files.
72
+ *
73
+ * @returns {Promise<void>}
74
+ */
75
+ async load() {
76
+ const globs = this.generateGlobs();
77
+ const replacements = this.generateReplacements();
78
+ await this.clearFiles();
79
+ await this.minify(globs, {replacements});
80
+ }
81
+ }
82
+
83
+ module.exports = MemberAttributionAssetsService;
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../../../.eslintrc.js",
3
+ "env": {
4
+ "browser": true,
5
+ "node": false
6
+ },
7
+ "rules": {
8
+ "no-console": "off"
9
+ }
10
+ }
@@ -0,0 +1,90 @@
1
+ // Location where we want to store the history in localStorage
2
+ const STORAGE_KEY = 'ghost-history';
3
+
4
+ // How long before an item should expire (24h)
5
+ const TIMEOUT = 24 * 60 * 60 * 1000;
6
+
7
+ // Maximum amount of urls in the history
8
+ const LIMIT = 15;
9
+
10
+ // History is saved in JSON format, from old to new
11
+ // Time is saved to be able to exclude old items
12
+ // [
13
+ // {
14
+ // "time": 12341234,
15
+ // "path": "/about/"
16
+ // },
17
+ // {
18
+ // "time": 12341235,
19
+ // "path": "/welcome/"
20
+ // }
21
+ // ]
22
+
23
+ (async function () {
24
+ try {
25
+ const storage = window.localStorage;
26
+ const historyString = storage.getItem(STORAGE_KEY);
27
+ const currentTime = new Date().getTime();
28
+
29
+ // Append current location
30
+ let history = [];
31
+
32
+ if (historyString) {
33
+ try {
34
+ history = JSON.parse(historyString);
35
+ } catch (error) {
36
+ // Ignore invalid JSON, ans clear history
37
+ console.warn('[Member Attribution] Error while parsing history', error);
38
+ }
39
+ }
40
+
41
+ // Remove all items that are expired
42
+ const firstNotExpiredIndex = history.findIndex((item) => {
43
+ // Return true to keep all items after and including this item
44
+ // Return false to remove the item
45
+
46
+ if (!item.time || typeof item.time !== 'number') {
47
+ return false;
48
+ }
49
+
50
+ const difference = currentTime - item.time;
51
+
52
+ if (isNaN(item.time) || difference > TIMEOUT) {
53
+ // Expired or invalid
54
+ return false;
55
+ }
56
+
57
+ // Valid item (so all following items are also valid by definition)
58
+ return true;
59
+ });
60
+
61
+ if (firstNotExpiredIndex > 0) {
62
+ // Remove until the first valid item
63
+ history.splice(0, firstNotExpiredIndex);
64
+ } else if (firstNotExpiredIndex === -1) {
65
+ // Not a single valid item found, remove all
66
+ history = [];
67
+ }
68
+
69
+ const currentPath = window.location.pathname;
70
+
71
+ if (history.length === 0 || history[history.length - 1].path !== currentPath) {
72
+ history.push({
73
+ path: currentPath,
74
+ time: currentTime
75
+ });
76
+ } else if (history.length > 0) {
77
+ history[history.length - 1].time = currentTime;
78
+ }
79
+
80
+ // Restrict length
81
+ if (history.length > LIMIT) {
82
+ history = history.slice(-LIMIT);
83
+ }
84
+
85
+ // Save current timestamp
86
+ storage.setItem(STORAGE_KEY, JSON.stringify(history));
87
+ } catch (error) {
88
+ console.error('[Member Attribution] Failed with error', error);
89
+ }
90
+ })();
@@ -75,6 +75,9 @@ module.exports = function setupSiteApp(routerConfig) {
75
75
  // Comment counts
76
76
  siteApp.use(mw.servePublicFile('built', 'public/comment-counts.min.js', 'application/javascript', constants.ONE_YEAR_S));
77
77
 
78
+ // Member attribution
79
+ siteApp.use(mw.servePublicFile('built', 'public/member-attribution.min.js', 'application/javascript', constants.ONE_YEAR_S));
80
+
78
81
  // Serve blog images using the storage adapter
79
82
  siteApp.use(STATIC_IMAGE_URL_PREFIX, mw.handleImageSizes, storage.getStorage('images').serve());
80
83
  // Serve blog media using the storage adapter
@@ -0,0 +1,7 @@
1
+ const Memory = require('./Memory');
2
+
3
+ class ImageSizesCacheSyncInMemory extends Memory {
4
+
5
+ }
6
+
7
+ module.exports = ImageSizesCacheSyncInMemory;
@@ -0,0 +1,7 @@
1
+ const Memory = require('./Memory');
2
+
3
+ class SettingsCacheSyncInMemory extends Memory {
4
+
5
+ }
6
+
7
+ module.exports = SettingsCacheSyncInMemory;
@@ -189,8 +189,6 @@ module.exports = {
189
189
  validation: {},
190
190
  permissions: true,
191
191
  query(frame) {
192
- frame.options.require = true;
193
-
194
192
  // TODO: move to likes service
195
193
  if (frame.options?.context?.member?.id) {
196
194
  return models.CommentLike.destroy({
@@ -198,12 +196,17 @@ module.exports = {
198
196
  destroyBy: {
199
197
  member_id: frame.options.context.member.id,
200
198
  comment_id: frame.options.id
201
- }
199
+ },
200
+ require: true
202
201
  }).then(() => null)
203
- .catch(models.CommentLike.NotFoundError, () => {
204
- return Promise.reject(new errors.NotFoundError({
205
- message: tpl(messages.likeNotFound)
206
- }));
202
+ .catch((err) => {
203
+ if (err instanceof models.CommentLike.NotFoundError) {
204
+ return Promise.reject(new errors.NotFoundError({
205
+ message: tpl(messages.likeNotFound)
206
+ }));
207
+ }
208
+
209
+ throw err;
207
210
  });
208
211
  } else {
209
212
  return Promise.reject(new errors.NotFoundError({
@@ -76,15 +76,7 @@ module.exports = {
76
76
  },
77
77
  permissions: true,
78
78
  query(frame) {
79
- frame.options.require = true;
80
-
81
- return models.Invite.destroy(frame.options)
82
- .then(() => null)
83
- .catch(models.Invite.NotFoundError, () => {
84
- return Promise.reject(new errors.NotFoundError({
85
- message: tpl(messages.inviteNotFound)
86
- }));
87
- });
79
+ return models.Invite.destroy({...frame.options, require: true});
88
80
  }
89
81
  },
90
82
 
@@ -150,13 +150,7 @@ module.exports = {
150
150
  },
151
151
  permissions: true,
152
152
  query(frame) {
153
- return models.Label.destroy(frame.options)
154
- .then(() => null)
155
- .catch(models.Label.NotFoundError, () => {
156
- return Promise.reject(new errors.NotFoundError({
157
- message: tpl(messages.labelNotFound)
158
- }));
159
- });
153
+ return models.Label.destroy({...frame.options, require: true});
160
154
  }
161
155
  }
162
156
  };
@@ -1,6 +1,5 @@
1
1
  // NOTE: We must not cache references to membersService.api
2
2
  // as it is a getter and may change during runtime.
3
- const Promise = require('bluebird');
4
3
  const moment = require('moment-timezone');
5
4
  const errors = require('@tryghost/errors');
6
5
  const models = require('../../models');
@@ -253,20 +252,11 @@ module.exports = {
253
252
  },
254
253
  permissions: true,
255
254
  async query(frame) {
256
- frame.options.require = true;
257
- frame.options.cancelStripeSubscriptions = frame.options.cancel;
258
-
259
- await Promise.resolve(membersService.api.members.destroy({
255
+ return membersService.api.members.destroy({
260
256
  id: frame.options.id
261
- }, frame.options)).catch(models.Member.NotFoundError, () => {
262
- throw new errors.NotFoundError({
263
- message: tpl(messages.resourceNotFound, {
264
- resource: 'Member'
265
- })
266
- });
257
+ }, {
258
+ ...frame.options, require: true, cancelStripeSubscriptions: frame.options.cancel
267
259
  });
268
-
269
- return null;
270
260
  }
271
261
  },
272
262
 
@@ -49,7 +49,7 @@ module.exports = {
49
49
  const offer = await offersService.api.updateOffer({
50
50
  ...frame.data.offers[0],
51
51
  id: frame.options.id
52
- });
52
+ }, frame.options);
53
53
 
54
54
  if (!offer) {
55
55
  throw new errors.NotFoundError({
@@ -69,7 +69,7 @@ module.exports = {
69
69
  cacheInvalidate: true
70
70
  },
71
71
  async query(frame) {
72
- const offer = await offersService.api.createOffer(frame.data.offers[0]);
72
+ const offer = await offersService.api.createOffer(frame.data.offers[0], frame.options);
73
73
  return {
74
74
  data: [offer]
75
75
  };
@@ -2,7 +2,7 @@ const models = require('../../models');
2
2
  const tpl = require('@tryghost/tpl');
3
3
  const errors = require('@tryghost/errors');
4
4
  const getPostServiceInstance = require('../../services/posts/posts-service');
5
- const ALLOWED_INCLUDES = ['tags', 'authors', 'authors.roles', 'tiers'];
5
+ const ALLOWED_INCLUDES = ['tags', 'authors', 'authors.roles', 'tiers', 'count.signups', 'count.conversions'];
6
6
  const UNSAFE_ATTRS = ['status', 'authors', 'visibility'];
7
7
 
8
8
  const messages = {
@@ -186,15 +186,7 @@ module.exports = {
186
186
  unsafeAttrs: UNSAFE_ATTRS
187
187
  },
188
188
  query(frame) {
189
- frame.options.require = true;
190
-
191
- return models.Post.destroy(frame.options)
192
- .then(() => null)
193
- .catch(models.Post.NotFoundError, () => {
194
- return Promise.reject(new errors.NotFoundError({
195
- message: tpl(messages.pageNotFound)
196
- }));
197
- });
189
+ return models.Post.destroy({...frame.options, require: true});
198
190
  }
199
191
  }
200
192
  };
@@ -2,7 +2,16 @@ const models = require('../../models');
2
2
  const tpl = require('@tryghost/tpl');
3
3
  const errors = require('@tryghost/errors');
4
4
  const getPostServiceInstance = require('../../services/posts/posts-service');
5
- const allowedIncludes = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter'];
5
+ const allowedIncludes = [
6
+ 'tags',
7
+ 'authors',
8
+ 'authors.roles',
9
+ 'email',
10
+ 'tiers',
11
+ 'newsletter',
12
+ 'count.signups',
13
+ 'count.conversions'
14
+ ];
6
15
  const unsafeAttrs = ['status', 'authors', 'visibility'];
7
16
 
8
17
  const messages = {
@@ -183,15 +192,7 @@ module.exports = {
183
192
  unsafeAttrs: unsafeAttrs
184
193
  },
185
194
  query(frame) {
186
- frame.options.require = true;
187
-
188
- return models.Post.destroy(frame.options)
189
- .then(() => null)
190
- .catch(models.Post.NotFoundError, () => {
191
- return Promise.reject(new errors.NotFoundError({
192
- message: tpl(messages.postNotFound)
193
- }));
194
- });
195
+ return models.Post.destroy({...frame.options, require: true});
195
196
  }
196
197
  }
197
198
  };
@@ -101,15 +101,7 @@ module.exports = {
101
101
  },
102
102
  permissions: true,
103
103
  query(frame) {
104
- frame.options.require = true;
105
-
106
- return models.Snippet.destroy(frame.options)
107
- .then(() => null)
108
- .catch(models.Snippet.NotFoundError, () => {
109
- return Promise.reject(new errors.NotFoundError({
110
- message: tpl(messages.snippetNotFound)
111
- }));
112
- });
104
+ return models.Snippet.destroy({...frame.options, require: true});
113
105
  }
114
106
  }
115
107
  };
@@ -147,13 +147,7 @@ module.exports = {
147
147
  },
148
148
  permissions: true,
149
149
  query(frame) {
150
- return models.Tag.destroy(frame.options)
151
- .then(() => null)
152
- .catch(models.Tag.NotFoundError, () => {
153
- return Promise.reject(new errors.NotFoundError({
154
- message: tpl(messages.tagNotFound)
155
- }));
156
- });
150
+ return models.Tag.destroy({...frame.options, require: true});
157
151
  }
158
152
  }
159
153
  };
@@ -23,7 +23,7 @@ function defaultRelations(frame) {
23
23
  return false;
24
24
  }
25
25
 
26
- frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'tiers'];
26
+ frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'tiers', 'count.signups', 'count.conversions'];
27
27
  }
28
28
 
29
29
  function setDefaultOrder(frame) {
@@ -23,7 +23,7 @@ function defaultRelations(frame) {
23
23
  return false;
24
24
  }
25
25
 
26
- frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter'];
26
+ frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter', 'count.signups', 'count.conversions'];
27
27
  }
28
28
 
29
29
  function setDefaultOrder(frame) {
@@ -10,6 +10,7 @@ const extraAttrs = require('../utils/extra-attrs');
10
10
  const gating = require('../utils/post-gating');
11
11
  const url = require('../utils/url');
12
12
 
13
+ const labs = require('../../../../../../../shared/labs');
13
14
  const utils = require('../../../index');
14
15
 
15
16
  const postsMetaSchema = require('../../../../../../data/schema').tables.posts_meta;
@@ -109,5 +110,9 @@ module.exports = async (model, frame, options = {}) => {
109
110
  });
110
111
  }
111
112
 
113
+ if (!labs.isSet('memberAttribution')) {
114
+ delete jsonModel.count;
115
+ }
116
+
112
117
  return jsonModel;
113
118
  };
@@ -128,7 +128,8 @@ function serializeMember(member, options) {
128
128
  email_open_rate: json.email_open_rate,
129
129
  email_recipients: json.email_recipients,
130
130
  status: json.status,
131
- last_seen_at: json.last_seen_at
131
+ last_seen_at: json.last_seen_at,
132
+ attribution: json.attribution
132
133
  };
133
134
 
134
135
  if (json.products) {
@@ -12,6 +12,7 @@ module.exports = {
12
12
  'logo',
13
13
  'icon',
14
14
  'accent_color',
15
+ 'locale',
15
16
  'url',
16
17
  'version',
17
18
  'sentry_dsn',
@@ -32,6 +32,9 @@ const author = (attrs, frame) => {
32
32
  delete attrs.status;
33
33
  delete attrs.email;
34
34
  delete attrs.comment_notifications;
35
+ delete attrs.free_member_signup_notification;
36
+ delete attrs.paid_subscription_started_notification;
37
+ delete attrs.paid_subscription_canceled_notification;
35
38
 
36
39
  // @NOTE: used for night shift
37
40
  delete attrs.accessibility;
@@ -130,9 +133,6 @@ const post = (attrs, frame) => {
130
133
 
131
134
  const action = (attrs) => {
132
135
  if (attrs.actor) {
133
- delete attrs.actor_id;
134
- delete attrs.resource_id;
135
-
136
136
  if (attrs.actor_type === 'user') {
137
137
  attrs.actor = _.pick(attrs.actor, ['id', 'name', 'slug', 'profile_image']);
138
138
  attrs.actor.image = attrs.actor.profile_image;
@@ -142,12 +142,11 @@ const action = (attrs) => {
142
142
  attrs.actor.image = attrs.actor.icon_image;
143
143
  delete attrs.actor.icon_image;
144
144
  }
145
- } else if (attrs.resource) {
146
- delete attrs.actor_id;
147
- delete attrs.resource_id;
145
+ }
148
146
 
147
+ if (attrs.resource) {
149
148
  // @NOTE: we only support posts right now
150
- attrs.resource = _.pick(attrs.resource, ['id', 'title', 'slug', 'feature_image']);
149
+ attrs.resource = _.pick(attrs.resource, ['id', 'title', 'slug', 'feature_image', 'name']);
151
150
  attrs.resource.image = attrs.resource.feature_image;
152
151
  delete attrs.resource.feature_image;
153
152
  }
@@ -78,14 +78,7 @@ module.exports = {
78
78
  }
79
79
  },
80
80
  query({data, options}) {
81
- return models.Webhook.edit(data.webhooks[0], Object.assign(options, {require: true}))
82
- .catch(models.Webhook.NotFoundError, () => {
83
- throw new errors.NotFoundError({
84
- message: tpl(messages.resourceNotFound, {
85
- resource: 'Webhook'
86
- })
87
- });
88
- });
81
+ return models.Webhook.edit(data.webhooks[0], {...options, require: true});
89
82
  }
90
83
  },
91
84
 
@@ -130,17 +123,7 @@ module.exports = {
130
123
  }
131
124
  },
132
125
  query(frame) {
133
- frame.options.require = true;
134
-
135
- return models.Webhook.destroy(frame.options)
136
- .then(() => null)
137
- .catch(models.Webhook.NotFoundError, () => {
138
- return Promise.reject(new errors.NotFoundError({
139
- message: tpl(messages.resourceNotFound, {
140
- resource: 'Webhook'
141
- })
142
- }));
143
- });
126
+ return models.Webhook.destroy({...frame.options, require: true});
144
127
  }
145
128
  }
146
129
  };
@@ -31,6 +31,8 @@ const BACKUP_TABLES = [
31
31
  'members_paid_subscription_events',
32
32
  'members_subscribe_events',
33
33
  'members_product_events',
34
+ 'members_created_events',
35
+ 'members_subscription_created_events',
34
36
  'members_newsletters',
35
37
  'comments',
36
38
  'comment_likes',