ghost 4.22.3 → 4.25.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 (230) hide show
  1. package/.c8rc.json +24 -0
  2. package/.eslintrc.js +39 -0
  3. package/Gruntfile.js +0 -1
  4. package/content/public/README.md +3 -0
  5. package/content/themes/casper/assets/built/casper.js +1 -1
  6. package/content/themes/casper/assets/built/casper.js.map +1 -1
  7. package/content/themes/casper/assets/built/global.css +1 -1
  8. package/content/themes/casper/assets/built/global.css.map +1 -1
  9. package/content/themes/casper/assets/built/screen.css +1 -1
  10. package/content/themes/casper/assets/built/screen.css.map +1 -1
  11. package/content/themes/casper/assets/css/global.css +6 -1
  12. package/content/themes/casper/assets/css/screen.css +32 -216
  13. package/content/themes/casper/default.hbs +2 -2
  14. package/content/themes/casper/package.json +3 -2
  15. package/content/themes/casper/post.hbs +1 -1
  16. package/content/themes/casper/yarn.lock +173 -123
  17. package/core/app.js +12 -1
  18. package/core/boot.js +47 -28
  19. package/core/bridge.js +10 -10
  20. package/core/built/assets/{chunk.3.324fd0cc598c73650219.js → chunk.3.8f95b516d88ff4eec64c.js} +18 -18
  21. package/core/built/assets/ghost-dark-d690e732e17ffc794e2e59c1467ca282.css +1 -0
  22. package/core/built/assets/ghost.min-043bb7480a0810109b130f13b2a4235e.css +1 -0
  23. package/core/built/assets/{ghost.min-7da921f6c6cac3fe10da1ba104575440.js → ghost.min-bc72f685c1c9adc9885925c1412435a5.js} +563 -605
  24. package/core/built/assets/icons/audio-upload.svg +8 -0
  25. package/core/built/assets/icons/powered-by-tenor.svg +35 -0
  26. package/core/built/assets/icons/tenor.svg +7 -0
  27. package/core/built/assets/{vendor.min-413f887176a041e6dbf88214ca9a7481.js → vendor.min-d1234c632a54502777c34e50752fa3fc.js} +4622 -3631
  28. package/core/frontend/apps/amp/lib/helpers/amp_content.js +2 -2
  29. package/core/frontend/apps/amp/lib/views/amp.hbs +112 -0
  30. package/core/frontend/apps/private-blogging/index.js +1 -1
  31. package/core/frontend/apps/private-blogging/lib/router.js +1 -1
  32. package/core/frontend/services/apps/index.js +1 -1
  33. package/core/frontend/services/apps/loader.js +3 -3
  34. package/core/frontend/services/card-assets/index.js +0 -12
  35. package/core/frontend/services/card-assets/service.js +29 -28
  36. package/core/frontend/services/helpers/handlebars.js +1 -1
  37. package/core/frontend/services/routing/CollectionRouter.js +4 -5
  38. package/core/frontend/services/routing/EmailRouter.js +1 -1
  39. package/core/frontend/services/routing/ParentRouter.js +0 -8
  40. package/core/frontend/services/routing/PreviewRouter.js +1 -1
  41. package/core/frontend/services/routing/StaticPagesRouter.js +1 -1
  42. package/core/frontend/services/routing/StaticRoutesRouter.js +4 -4
  43. package/core/frontend/services/routing/TaxonomyRouter.js +3 -3
  44. package/core/frontend/services/routing/{middlewares → middleware}/index.js +0 -0
  45. package/core/frontend/services/routing/{middlewares → middleware}/page-param.js +0 -0
  46. package/core/frontend/services/routing/router-manager.js +7 -2
  47. package/core/frontend/services/rss/generate-feed.js +2 -1
  48. package/core/frontend/services/theme-engine/middleware/ensure-active-theme.js +34 -0
  49. package/core/frontend/services/theme-engine/middleware/index.js +6 -0
  50. package/core/frontend/services/theme-engine/middleware/update-global-template-options.js +116 -0
  51. package/core/frontend/services/theme-engine/middleware/update-local-template-data.js +9 -0
  52. package/core/frontend/services/theme-engine/middleware/update-local-template-options.js +57 -0
  53. package/core/frontend/src/cards/css/bookmark.css +72 -47
  54. package/core/frontend/src/cards/css/button.css +4 -0
  55. package/core/frontend/src/cards/css/callout.css +40 -3
  56. package/core/frontend/src/cards/css/gallery.css +15 -10
  57. package/core/frontend/src/cards/css/nft.css +20 -11
  58. package/core/frontend/src/cards/css/toggle.css +58 -0
  59. package/core/frontend/src/cards/js/toggle.js +16 -0
  60. package/core/frontend/web/middleware/error-handler.js +93 -0
  61. package/core/frontend/web/middleware/handle-image-sizes.js +3 -6
  62. package/core/frontend/web/middleware/index.js +1 -0
  63. package/core/frontend/web/middleware/serve-public-file.js +39 -16
  64. package/core/frontend/web/site.js +11 -14
  65. package/core/server/adapters/scheduling/SchedulingDefault.js +2 -2
  66. package/core/server/adapters/storage/LocalStorageBase.js +2 -2
  67. package/core/server/api/canary/authentication.js +1 -1
  68. package/core/server/api/canary/db.js +2 -2
  69. package/core/server/api/canary/media.js +3 -2
  70. package/core/server/api/canary/oembed.js +16 -1
  71. package/core/server/api/canary/session.js +1 -1
  72. package/core/server/api/canary/slugs.js +1 -1
  73. package/core/server/api/canary/utils/permissions.js +2 -2
  74. package/core/server/api/canary/utils/serializers/output/config.js +2 -6
  75. package/core/server/api/v2/authentication.js +1 -1
  76. package/core/server/api/v2/db.js +2 -2
  77. package/core/server/api/v2/session.js +1 -1
  78. package/core/server/api/v2/slugs.js +1 -1
  79. package/core/server/api/v2/utils/permissions.js +2 -2
  80. package/core/server/api/v3/authentication.js +1 -1
  81. package/core/server/api/v3/db.js +2 -2
  82. package/core/server/api/v3/session.js +1 -1
  83. package/core/server/api/v3/slugs.js +1 -1
  84. package/core/server/api/v3/utils/permissions.js +2 -2
  85. package/core/server/data/db/connection.js +7 -0
  86. package/core/server/data/db/state-manager.js +4 -4
  87. package/core/server/data/exporter/export-filename.js +1 -1
  88. package/core/server/data/importer/handlers/json.js +1 -1
  89. package/core/server/data/importer/import-manager.js +1 -1
  90. package/core/server/data/importer/importers/data/base.js +1 -1
  91. package/core/server/data/importer/importers/data/data-importer.js +3 -3
  92. package/core/server/data/migrations/init/2-create-fixtures.js +3 -20
  93. package/core/server/data/migrations/utils.js +2 -2
  94. package/core/server/data/migrations/versions/1.21/1-add-contributor-role.js +5 -5
  95. package/core/server/data/migrations/versions/1.25/1-update-koenig-beta-html.js +1 -0
  96. package/core/server/data/migrations/versions/2.15/2-insert-zapier-integration.js +3 -3
  97. package/core/server/data/migrations/versions/2.2/3-insert-admin-integration-role.js +5 -5
  98. package/core/server/data/migrations/versions/2.27/1-insert-ghost-db-backup-role.js +5 -6
  99. package/core/server/data/migrations/versions/2.27/2-insert-db-backup-integration.js +3 -4
  100. package/core/server/data/migrations/versions/2.28/3-insert-ghost-scheduler-role.js +7 -7
  101. package/core/server/data/migrations/versions/2.28/4-insert-scheduler-integration.js +3 -3
  102. package/core/server/data/migrations/versions/3.1/08-add-uuid-values-to-members.js +1 -0
  103. package/core/server/data/migrations/versions/3.22/02-settings-key-renames.js +2 -0
  104. package/core/server/data/migrations/versions/3.22/05-migrate-members-subscription-settings.js +3 -0
  105. package/core/server/data/migrations/versions/3.22/06-migrate-stripe-connect-settings.js +2 -0
  106. package/core/server/data/migrations/versions/3.23/01-migrate-bulk-email-settings.js +1 -0
  107. package/core/server/data/migrations/versions/3.29/01-remove-duplicate-subscriptions.js +2 -0
  108. package/core/server/data/migrations/versions/3.29/02-remove-duplicate-customers.js +2 -0
  109. package/core/server/data/migrations/versions/3.38/04-populate-recipient-filter-column.js +2 -0
  110. package/core/server/data/migrations/versions/4.0/01-update-mobiledoc.js +2 -0
  111. package/core/server/data/migrations/versions/4.0/03-populate-status-column-for-members.js +4 -0
  112. package/core/server/data/migrations/versions/4.0/06-populate-members-subscribe-events-table.js +1 -0
  113. package/core/server/data/migrations/versions/4.0/17-populate-members-status-events-table.js +1 -0
  114. package/core/server/data/migrations/versions/4.0/18-transform-urls-absolute-to-transform-ready.js +5 -0
  115. package/core/server/data/migrations/versions/4.0/22-solve-orphaned-webhooks.js +1 -0
  116. package/core/server/data/migrations/versions/4.0/23-regenerate-posts-html.js +1 -0
  117. package/core/server/data/migrations/versions/4.0/25-populate-members-paid-subscription-events-table.js +2 -1
  118. package/core/server/data/migrations/versions/4.12/02-fix-member-statuses.js +1 -0
  119. package/core/server/data/migrations/versions/4.14/01-fix-comped-member-statuses.js +3 -0
  120. package/core/server/data/migrations/versions/4.14/02-fix-free-members-status-events.js +1 -0
  121. package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +2 -0
  122. package/core/server/data/migrations/versions/4.23/01-truncate-offer-names.js +59 -0
  123. package/core/server/data/migrations/versions/4.3/04-attach-members-to-product.js +1 -0
  124. package/core/server/data/migrations/versions/4.4/01-restore-free-members-signup-setting-from-backup.js +1 -0
  125. package/core/server/data/migrations/versions/4.6/01-remove-comped-status.js +1 -0
  126. package/core/server/data/migrations/versions/4.8/04-migrate-show-newsletter-header-setting.js +1 -0
  127. package/core/server/data/migrations/versions/4.9/05-fix-missed-mobiledoc-url-transforms.js +1 -0
  128. package/core/server/data/migrations/versions/4.9/06-add-comped-status.js +1 -0
  129. package/core/server/data/migrations/versions/4.9/07-update-comped-members-status-events.js +1 -0
  130. package/core/server/data/schema/commands.js +2 -2
  131. package/core/server/data/schema/fixtures/fixture-manager.js +340 -0
  132. package/core/server/data/schema/fixtures/index.js +8 -2
  133. package/core/server/ghost-server.js +2 -2
  134. package/core/server/lib/image/image-size.js +2 -2
  135. package/core/server/models/base/listeners.js +2 -2
  136. package/core/server/models/member-email-change-event.js +2 -2
  137. package/core/server/models/member-login-event.js +2 -2
  138. package/core/server/models/member-paid-subscription-event.js +3 -3
  139. package/core/server/models/member-payment-event.js +3 -3
  140. package/core/server/models/member-product-event.js +6 -6
  141. package/core/server/models/member-status-event.js +5 -3
  142. package/core/server/models/member-subscribe-event.js +9 -3
  143. package/core/server/models/relations/authors.js +1 -1
  144. package/core/server/models/settings.js +1 -1
  145. package/core/server/services/auth/passwordreset.js +1 -1
  146. package/core/server/services/auth/setup.js +1 -1
  147. package/core/server/services/email-analytics/jobs/index.js +1 -1
  148. package/core/server/services/mega/mega.js +6 -4
  149. package/core/server/services/mega/post-email-serializer.js +5 -1
  150. package/core/server/services/mega/segment-parser.js +1 -2
  151. package/core/server/services/mega/template.js +52 -37
  152. package/core/server/services/members/api.js +22 -0
  153. package/core/server/services/members/config.js +1 -1
  154. package/core/server/services/members/emails/signup-paid.js +168 -0
  155. package/core/server/services/members/service.js +6 -2
  156. package/core/server/services/members/stripe-connect.js +4 -2
  157. package/core/server/services/nft-oembed.js +13 -22
  158. package/core/server/services/oembed.js +28 -24
  159. package/core/server/services/permissions/can-this.js +1 -1
  160. package/core/server/services/public-config/config.js +1 -1
  161. package/core/server/services/redirects/api.js +20 -25
  162. package/core/server/services/redirects/index.js +18 -10
  163. package/core/server/services/redirects/utils.js +14 -0
  164. package/core/server/services/redirects/validation.js +10 -0
  165. package/core/server/services/route-settings/default-settings-manager.js +1 -1
  166. package/core/server/services/route-settings/index.js +40 -17
  167. package/core/server/services/route-settings/route-settings.js +120 -115
  168. package/core/server/services/route-settings/settings-loader.js +18 -36
  169. package/core/server/services/route-settings/yaml-parser.js +1 -1
  170. package/core/server/services/slack.js +1 -1
  171. package/core/server/services/themes/activation-bridge.js +3 -3
  172. package/core/server/services/themes/storage.js +2 -2
  173. package/core/server/services/twitter-embed.js +80 -0
  174. package/core/server/services/url/LocalFileCache.js +75 -0
  175. package/core/server/services/url/Resources.js +8 -2
  176. package/core/server/services/url/UrlGenerator.js +23 -20
  177. package/core/server/services/url/UrlService.js +75 -63
  178. package/core/server/services/url/index.js +17 -3
  179. package/core/server/services/xmlrpc.js +2 -2
  180. package/core/server/web/admin/app.js +7 -10
  181. package/core/server/web/admin/controller.js +35 -12
  182. package/core/server/web/admin/middleware/redirect-admin-urls.js +15 -0
  183. package/core/server/web/admin/views/default-prod.html +4 -4
  184. package/core/server/web/admin/views/default.html +4 -4
  185. package/core/server/web/api/app.js +1 -1
  186. package/core/server/web/api/canary/admin/app.js +3 -6
  187. package/core/server/web/api/canary/admin/middleware.js +7 -7
  188. package/core/server/web/api/canary/admin/routes.js +5 -5
  189. package/core/server/web/api/canary/content/app.js +3 -6
  190. package/core/server/web/api/canary/content/middleware.js +3 -3
  191. package/core/server/web/api/v2/admin/app.js +3 -6
  192. package/core/server/web/api/v2/admin/middleware.js +7 -7
  193. package/core/server/web/api/v2/admin/routes.js +5 -5
  194. package/core/server/web/api/v2/content/app.js +3 -6
  195. package/core/server/web/api/v2/content/middleware.js +3 -3
  196. package/core/server/web/api/v3/admin/app.js +3 -6
  197. package/core/server/web/api/v3/admin/middleware.js +7 -7
  198. package/core/server/web/api/v3/admin/routes.js +5 -5
  199. package/core/server/web/api/v3/content/app.js +3 -6
  200. package/core/server/web/api/v3/content/middleware.js +3 -3
  201. package/core/server/web/members/app.js +6 -9
  202. package/core/server/web/oauth/app.js +0 -4
  203. package/core/server/web/parent/app.js +17 -9
  204. package/core/server/web/parent/frontend.js +1 -1
  205. package/core/server/web/shared/index.js +2 -2
  206. package/core/server/web/shared/{middlewares → middleware}/api/index.js +0 -0
  207. package/core/server/web/shared/{middlewares → middleware}/api/spam-prevention.js +0 -0
  208. package/core/server/web/shared/{middlewares → middleware}/brute.js +0 -0
  209. package/core/server/web/shared/{middlewares → middleware}/cache-control.js +0 -0
  210. package/core/server/web/shared/middleware/error-handler.js +224 -0
  211. package/core/server/web/shared/{middlewares → middleware}/index.js +0 -4
  212. package/core/server/web/shared/{middlewares → middleware}/pretty-urls.js +0 -0
  213. package/core/server/web/shared/{middlewares → middleware}/uncapitalise.js +0 -0
  214. package/core/server/web/shared/{middlewares → middleware}/url-redirects.js +0 -0
  215. package/core/shared/config/defaults.json +13 -1
  216. package/core/shared/config/helpers.js +42 -0
  217. package/core/shared/config/loader.js +1 -1
  218. package/core/shared/labs.js +9 -5
  219. package/core/shared/sentry.js +1 -1
  220. package/loggingrc.js +19 -20
  221. package/package.json +38 -37
  222. package/yarn.lock +1064 -892
  223. package/content/themes/casper/assets/js/gallery-card.js +0 -24
  224. package/core/built/assets/ghost-dark-39fb496d051565531062d7e047d1c0b1.css +0 -1
  225. package/core/built/assets/ghost.min-4207edfc1ae0a3f9f6505ca00d20b0c0.css +0 -1
  226. package/core/frontend/services/theme-engine/middleware.js +0 -209
  227. package/core/server/data/schema/fixtures/utils.js +0 -321
  228. package/core/server/web/parent/vhost-utils.js +0 -39
  229. package/core/server/web/shared/middlewares/error-handler.js +0 -329
  230. package/core/server/web/shared/middlewares/maintenance.js +0 -25
@@ -131,7 +131,7 @@ const transformEmailRecipientFilter = (emailRecipientFilter, {errorProperty = 'e
131
131
  // `paid` and `free` were swapped out for NQL filters in 4.5.0, we shouldn't see them here now
132
132
  case 'paid':
133
133
  case 'free':
134
- throw new errors.GhostError({
134
+ throw new errors.InternalServerError({
135
135
  message: tpl(messages.unexpectedFilterError, {
136
136
  property: errorProperty,
137
137
  value: emailRecipientFilter
@@ -140,7 +140,7 @@ const transformEmailRecipientFilter = (emailRecipientFilter, {errorProperty = 'e
140
140
  case 'all':
141
141
  return 'subscribed:true';
142
142
  case 'none':
143
- throw new errors.GhostError({
143
+ throw new errors.InternalServerError({
144
144
  message: tpl(messages.noneFilterError, {
145
145
  property: errorProperty
146
146
  })
@@ -352,7 +352,7 @@ async function sendEmailJob({emailModel, options}) {
352
352
  error: errorMessage
353
353
  }, {patch: true});
354
354
 
355
- throw new errors.GhostError({
355
+ throw new errors.InternalServerError({
356
356
  err: error,
357
357
  context: tpl(messages.sendEmailRequestFailed)
358
358
  });
@@ -416,7 +416,9 @@ function partitionMembersBySegment(memberRows, segments) {
416
416
  segmentedMemberRows = memberRows.filter(member => member.status !== 'free');
417
417
  memberRows = memberRows.filter(member => member.status === 'free');
418
418
  } else {
419
- throw new errors.ValidationError(tpl(messages.invalidSegment));
419
+ throw new errors.ValidationError({
420
+ message: tpl(messages.invalidSegment)
421
+ });
420
422
  }
421
423
 
422
424
  partitions[memberSegment] = segmentedMemberRows;
@@ -3,7 +3,6 @@ const template = require('./template');
3
3
  const settingsCache = require('../../../shared/settings-cache');
4
4
  const urlUtils = require('../../../shared/url-utils');
5
5
  const moment = require('moment-timezone');
6
- const cheerio = require('cheerio');
7
6
  const api = require('../../api');
8
7
  const {URL} = require('url');
9
8
  const mobiledocLib = require('../../lib/mobiledoc');
@@ -24,6 +23,8 @@ const formatHtmlForEmail = function formatHtmlForEmail(html) {
24
23
 
25
24
  // convert juiced HTML to a DOM-like interface for further manipulation
26
25
  // happens after inlining of CSS so we can change element types without worrying about styling
26
+
27
+ const cheerio = require('cheerio');
27
28
  const _cheerio = cheerio.load(juicedHtml);
28
29
 
29
30
  // force all links to open in new tab
@@ -243,6 +244,7 @@ const serialize = async (postModel, options = {isBrowserPreview: false, apiVersi
243
244
 
244
245
  // perform any email specific adjustments to the mobiledoc->HTML render output
245
246
  // body wrapper is required so we can get proper top-level selections
247
+ const cheerio = require('cheerio');
246
248
  let _cheerio = cheerio.load(`<body>${post.html}</body>`);
247
249
  // remove leading/trailing HRs
248
250
  _cheerio(`
@@ -311,6 +313,8 @@ const serialize = async (postModel, options = {isBrowserPreview: false, apiVersi
311
313
  };
312
314
 
313
315
  function renderEmailForSegment(email, memberSegment) {
316
+ const cheerio = require('cheerio');
317
+
314
318
  const result = {...email};
315
319
  const $ = cheerio.load(result.html);
316
320
 
@@ -1,6 +1,5 @@
1
- const cheerio = require('cheerio');
2
-
3
1
  const getSegmentsFromHtml = (html) => {
2
+ const cheerio = require('cheerio');
4
3
  const $ = cheerio.load(html);
5
4
 
6
5
  const allSegments = $('[data-gh-segment]')
@@ -544,60 +544,75 @@ figure blockquote p {
544
544
  border-width: 0.8em 0 0.8em 1.5em;
545
545
  }
546
546
 
547
- a.kg-nft-card {
547
+ .kg-nft-link {
548
+ display: block;
549
+ text-decoration: none !important;
550
+ color: #15212A !important;
551
+ font-family: inherit !important;
552
+ font-size: 14px;
553
+ line-height: 1.3em;
554
+ padding-top: 4px;
555
+ padding-right: 20px;
556
+ padding-left: 20px;
557
+ padding-bottom: 4px;
558
+ }
559
+
560
+ .kg-callout-card {
548
561
  display: flex;
549
- flex-direction: column;
550
- color: #15212A;
551
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
552
- font-size: 15px;
553
- text-decoration: none;
562
+ margin: 0 0 1.5em 0;
563
+ padding: 20px 28px;
554
564
  border-radius: 3px;
555
- border: 1px solid #e5eff5;
556
- width: 100%;
557
- max-width: 512px;
558
- color: #222;
565
+ }
566
+
567
+ .kg-callout-card p {
568
+ margin: 0
569
+ }
570
+
571
+ .kg-callout-card-grey {
572
+ background: #eef0f2;
573
+ }
574
+
575
+ .kg-callout-card-white {
559
576
  background: #fff;
560
- margin: 0 auto;
577
+ box-shadow: inset 0 0 0 1px #dddedf;
561
578
  }
562
579
 
563
- .kg-nft-metadata {
564
- padding: 20px;
580
+ .kg-callout-card-blue {
581
+ background: #E9F6FB;
565
582
  }
566
583
 
567
- .kg-nft-image {
568
- border-radius: 2px 2px 0 0;
584
+ .kg-callout-card-green {
585
+ background: #E8F8EA;
569
586
  }
570
587
 
571
- .kg-nft-header {
572
- display: flex;
573
- justify-content: space-between;
574
- gap: 20px;
588
+ .kg-callout-card-yellow {
589
+ background: #FCF4E3;
575
590
  }
576
591
 
577
- .kg-nft-title {
578
- font-family: inherit;
579
- font-size: 19px;
580
- font-weight: 700;
581
- margin: 0;
582
- color: #222;
592
+ .kg-callout-card-red {
593
+ background: #FBE9E9;
583
594
  }
584
595
 
585
- .kg-nft-creator {
586
- font-family: inherit;
587
- margin: 4px 0 0;
588
- color: #ababab;
596
+ .kg-callout-card-pink {
597
+ background: #FCEEF8;
589
598
  }
590
599
 
591
- .kg-nft-creator-name {
592
- font-weight: 500;
593
- color: #222;
600
+ .kg-callout-card-purple {
601
+ background: #F2EDFC;
594
602
  }
595
603
 
596
- .kg-nft-description {
597
- font-family: inherit;
598
- line-height: 1.4em;
599
- margin: 12px 0 0;
600
- color: #222;
604
+ .kg-callout-card-accent {
605
+ background: ${templateSettings.accentColor || '#15212A'};
606
+ color: #fff;
607
+ }
608
+
609
+ .kg-callout-card-accent a {
610
+ color: #fff;
611
+ }
612
+
613
+ .kg-callout-emoji {
614
+ padding-right: 12px;
615
+ font-size: 20px;
601
616
  }
602
617
 
603
618
  /* -------------------------------------
@@ -6,6 +6,7 @@ const mail = require('../mail');
6
6
  const models = require('../../models');
7
7
  const signinEmail = require('./emails/signin');
8
8
  const signupEmail = require('./emails/signup');
9
+ const signupPaidEmail = require('./emails/signup-paid');
9
10
  const subscribeEmail = require('./emails/subscribe');
10
11
  const updateEmail = require('./emails/updateEmail');
11
12
  const SingleUseTokenProvider = require('./SingleUseTokenProvider');
@@ -49,6 +50,8 @@ function createApiInstance(config) {
49
50
  return `📫 Confirm your subscription to ${siteTitle}`;
50
51
  case 'signup':
51
52
  return `🙌 Complete your sign up to ${siteTitle}!`;
53
+ case 'signup-paid':
54
+ return `🙌 Thank you for signing up to ${siteTitle}!`;
52
55
  case 'updateEmail':
53
56
  return `📫 Confirm your email update for ${siteTitle}!`;
54
57
  case 'signin':
@@ -93,6 +96,23 @@ function createApiInstance(config) {
93
96
  Sent to ${email}
94
97
  If you did not make this request, you can simply delete this message. You will not be signed up, and no account will be created for you.
95
98
  `;
99
+ case 'signup-paid':
100
+ return `
101
+ Thank you for subscribing to ${siteTitle}!
102
+
103
+ Tap the link below to be automatically signed in:
104
+
105
+ ${url}
106
+
107
+ For your security, the link will expire in 24 hours time.
108
+
109
+ See you soon!
110
+
111
+ ---
112
+
113
+ Sent to ${email}
114
+ Thank you for subscribing to ${siteTitle}!
115
+ `;
96
116
  case 'updateEmail':
97
117
  return `
98
118
  Hey there,
@@ -139,6 +159,8 @@ function createApiInstance(config) {
139
159
  return subscribeEmail({url, email, siteTitle, accentColor, siteDomain, siteUrl});
140
160
  case 'signup':
141
161
  return signupEmail({url, email, siteTitle, accentColor, siteDomain, siteUrl});
162
+ case 'signup-paid':
163
+ return signupPaidEmail({url, email, siteTitle, accentColor, siteDomain, siteUrl});
142
164
  case 'updateEmail':
143
165
  return updateEmail({url, email, siteTitle, accentColor, siteDomain, siteUrl});
144
166
  case 'signin':
@@ -98,7 +98,7 @@ class MembersConfigProvider {
98
98
  */
99
99
  getStripeKeys(type) {
100
100
  if (type !== 'direct' && type !== 'connect') {
101
- throw new errors.IncorrectUsageError(tpl(messages.incorrectKeyType));
101
+ throw new errors.IncorrectUsageError({message: tpl(messages.incorrectKeyType)});
102
102
  }
103
103
 
104
104
  const secretKey = this._settingsCache.get(`stripe_${type === 'connect' ? 'connect_' : ''}secret_key`);
@@ -0,0 +1,168 @@
1
+ module.exports = ({siteTitle, email, url, accentColor = '#15212A', siteDomain, siteUrl}) => `
2
+ <!doctype html>
3
+ <html>
4
+ <head>
5
+ <meta name="viewport" content="width=device-width">
6
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
7
+ <title>🙌 Thank you for signing up to ${siteTitle}!</title>
8
+ <style>
9
+ /* -------------------------------------
10
+ RESPONSIVE AND MOBILE FRIENDLY STYLES
11
+ ------------------------------------- */
12
+ @media only screen and (max-width: 620px) {
13
+ table[class=body] h1 {
14
+ font-size: 28px !important;
15
+ margin-bottom: 10px !important;
16
+ }
17
+ table[class=body] p,
18
+ table[class=body] ul,
19
+ table[class=body] ol,
20
+ table[class=body] td,
21
+ table[class=body] span,
22
+ table[class=body] a {
23
+ font-size: 16px !important;
24
+ }
25
+ table[class=body] .wrapper,
26
+ table[class=body] .article {
27
+ padding: 10px !important;
28
+ }
29
+ table[class=body] .content {
30
+ padding: 0 !important;
31
+ }
32
+ table[class=body] .container {
33
+ padding: 0 !important;
34
+ width: 100% !important;
35
+ }
36
+ table[class=body] .main {
37
+ border-left-width: 0 !important;
38
+ border-radius: 0 !important;
39
+ border-right-width: 0 !important;
40
+ }
41
+ table[class=body] .btn table {
42
+ width: 100% !important;
43
+ }
44
+ table[class=body] .btn a {
45
+ width: 100% !important;
46
+ }
47
+ table[class=body] .img-responsive {
48
+ height: auto !important;
49
+ max-width: 100% !important;
50
+ width: auto !important;
51
+ }
52
+ table[class=body] p[class=small],
53
+ table[class=body] a[class=small] {
54
+ font-size: 11px !important;
55
+ }
56
+ }
57
+ /* -------------------------------------
58
+ PRESERVE THESE STYLES IN THE HEAD
59
+ ------------------------------------- */
60
+ @media all {
61
+ .ExternalClass {
62
+ width: 100%;
63
+ }
64
+ .ExternalClass,
65
+ .ExternalClass p,
66
+ .ExternalClass span,
67
+ .ExternalClass font,
68
+ .ExternalClass td,
69
+ .ExternalClass div {
70
+ line-height: 100%;
71
+ }
72
+ .recipient-link a {
73
+ color: inherit !important;
74
+ font-family: inherit !important;
75
+ font-size: inherit !important;
76
+ font-weight: inherit !important;
77
+ line-height: inherit !important;
78
+ text-decoration: none !important;
79
+ }
80
+ #MessageViewBody a {
81
+ color: inherit;
82
+ text-decoration: none;
83
+ font-size: inherit;
84
+ font-family: inherit;
85
+ font-weight: inherit;
86
+ line-height: inherit;
87
+ }
88
+ }
89
+ hr {
90
+ border-width: 0;
91
+ height: 0;
92
+ margin-top: 34px;
93
+ margin-bottom: 34px;
94
+ border-bottom-width: 1px;
95
+ border-bottom-color: #EEF5F8;
96
+ }
97
+ a {
98
+ color: #3A464C;
99
+ }
100
+ </style>
101
+ </head>
102
+ <body class="" style="background-color: #FFFFFF; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.5em; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;">
103
+ <table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: #FFFFFF;">
104
+ <tr>
105
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">&nbsp;</td>
106
+ <td class="container" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; display: block; margin: 0 auto; max-width: 540px; padding: 10px; width: 540px;">
107
+ <div class="content" style="box-sizing: border-box; display: block; margin: 0 auto; max-width: 600px; padding: 30px 20px;">
108
+
109
+ <!-- START CENTERED WHITE CONTAINER -->
110
+ <span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Complete signup for ${siteTitle}!</span>
111
+ <table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #ffffff; border-radius: 8px;">
112
+
113
+ <!-- START MAIN CONTENT AREA -->
114
+ <tr>
115
+ <td class="wrapper" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; box-sizing: border-box;">
116
+ <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
117
+ <tr>
118
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
119
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 20px; color: #15212A; font-weight: bold; line-height: 25px; margin: 0; margin-bottom: 15px;">Thank you for subscribing to ${siteTitle}</p>
120
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 32px;">Tap the link below to be automatically signed in:</p>
121
+ <table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
122
+ <tbody>
123
+ <tr>
124
+ <td align="left" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; padding-bottom: 35px;">
125
+ <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
126
+ <tbody>
127
+ <tr>
128
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; background-color: ${accentColor}; border-radius: 5px; text-align: center;"> <a href="${url}" target="_blank" style="display: inline-block; color: #ffffff; background-color: ${accentColor}; border: solid 1px ${accentColor}; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 16px; font-weight: normal; margin: 0; padding: 9px 22px 10px; border-color: ${accentColor};">Confirm signup</a> </td>
129
+ </tr>
130
+ </tbody>
131
+ </table>
132
+ </td>
133
+ </tr>
134
+ </tbody>
135
+ </table>
136
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 25px;">For your security, the link will expire in 24 hours time.</p>
137
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 30px;">See you soon!</p>
138
+ <hr/>
139
+ <p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 5px;">You can also copy & paste this URL into your browser:</p>
140
+ <p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 25px; margin-top: 0; color: #3A464C;">${url}</p>
141
+ </td>
142
+ </tr>
143
+ </table>
144
+ </td>
145
+ </tr>
146
+
147
+ <!-- START FOOTER -->
148
+ <tr>
149
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-top: 2px;">
150
+ <p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 11px; color: #738A94; font-weight: normal; margin: 0; margin-bottom: 2px;">This message was sent from <a class="small" href="${siteUrl}" style="text-decoration: underline; color: #738A94; font-size: 11px;">${siteDomain}</a> to <a class="small" href="mailto:${email}" style="text-decoration: underline; color: #738A94; font-size: 11px;">${email}</a></p>
151
+ </td>
152
+ </tr>
153
+
154
+ <!-- END FOOTER -->
155
+
156
+ <!-- END MAIN CONTENT AREA -->
157
+ </table>
158
+
159
+ <!-- END CENTERED WHITE CONTAINER -->
160
+ </div>
161
+ </td>
162
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">&nbsp;</td>
163
+ </tr>
164
+ </table>
165
+ </body>
166
+ </html>
167
+ `;
168
+
@@ -159,12 +159,16 @@ const membersService = {
159
159
  }
160
160
 
161
161
  if (stripeService.api.configured && stripeService.api.mode === 'live') {
162
- throw new errors.IncorrectUsageError(tpl(messages.noLiveKeysInDevelopment));
162
+ throw new errors.IncorrectUsageError({
163
+ message: tpl(messages.noLiveKeysInDevelopment)
164
+ });
163
165
  }
164
166
  } else {
165
167
  const siteUrl = urlUtils.getSiteUrl();
166
168
  if (!/^https/.test(siteUrl) && stripeService.api.configured) {
167
- throw new errors.IncorrectUsageError(tpl(messages.sslRequiredForStripe));
169
+ throw new errors.IncorrectUsageError({
170
+ message: tpl(messages.sslRequiredForStripe)
171
+ });
168
172
  }
169
173
  }
170
174
  if (!membersApi) {
@@ -63,7 +63,7 @@ async function getStripeConnectTokenData(encodedData, getSessionProp) {
63
63
  const state = await getSessionProp(STATE_PROP);
64
64
 
65
65
  if (state !== data.s) {
66
- throw new errors.NoPermissionError(tpl(messages.incorrectState));
66
+ throw new errors.NoPermissionError({message: tpl(messages.incorrectState)});
67
67
  }
68
68
 
69
69
  return {
@@ -81,7 +81,9 @@ function checkCanConnect() {
81
81
  const siteUrlUsingSSL = /^https/.test(siteUrl);
82
82
  const cannotConnectToStripe = productionMode && !siteUrlUsingSSL;
83
83
  if (cannotConnectToStripe) {
84
- throw new errors.BadRequestError('Cannot connect to stripe unless site is using https://');
84
+ throw new errors.BadRequestError({
85
+ message: 'Cannot connect to stripe unless site is using https://'
86
+ });
85
87
  }
86
88
  }
87
89
 
@@ -28,42 +28,33 @@ class NFTOEmbedProvider {
28
28
  * @param {URL} url
29
29
  * @param {IExternalRequest} externalRequest
30
30
  *
31
- * @returns {Promise<import('oembed-parser').RichTypeData & Object<string, any>>}
31
+ * @returns {Promise<object>}
32
32
  */
33
33
  async getOEmbedData(url, externalRequest) {
34
34
  const [match, transaction, asset] = url.pathname.match(OPENSEA_PATH_REGEX);
35
35
  if (!match) {
36
36
  return null;
37
37
  }
38
- const result = await externalRequest(`https://api.opensea.io/api/v1/asset/${transaction}/${asset}/`, {
39
- json: true
38
+ const headers = {};
39
+ if (this.dependencies.config.apiKey) {
40
+ headers['X-API-KEY'] = this.dependencies.config.apiKey;
41
+ }
42
+ const result = await externalRequest(`https://api.opensea.io/api/v1/asset/${transaction}/${asset}/?format=json`, {
43
+ json: true,
44
+ headers
40
45
  });
41
46
  return {
42
47
  version: '1.0',
43
- type: 'rich',
48
+ type: 'nft',
44
49
  title: result.body.name,
45
50
  author_name: result.body.creator.user.username,
46
51
  author_url: `https://opensea.io/${result.body.creator.user.username}`,
47
52
  provider_name: 'OpenSea',
48
53
  provider_url: 'https://opensea.io',
49
- html: `
50
- <a href="${result.body.permalink}" class="kg-nft-card">
51
- <img class="kg-nft-image" src="${result.body.image_url}">
52
- <div class="kg-nft-metadata">
53
- <div class="kg-nft-header">
54
- <h4 class="kg-nft-title"> ${result.body.name} </h4>
55
- </div>
56
- <div class="kg-nft-creator">
57
- Created by <span class="kg-nft-creator-name">${result.body.creator.user.username}</span>
58
- ${(result.body.collection.name ? `&bull; ${result.body.collection.name}` : ``)}
59
- </div>
60
- ${(result.body.description ? `<p class="kg-nft-description">${result.body.description}</p>` : ``)}
61
- </div>
62
- </a>
63
- `,
64
- width: 1000,
65
- height: 1000,
66
- noIframe: true
54
+ image_url: result.body.image_url,
55
+ creator_name: result.body.creator.user.username,
56
+ description: result.body.description,
57
+ collection_name: result.body.collection.name
67
58
  };
68
59
  }
69
60
  }
@@ -1,5 +1,6 @@
1
1
  const errors = require('@tryghost/errors');
2
2
  const tpl = require('@tryghost/tpl');
3
+ const logging = require('@tryghost/logging');
3
4
  const {extract, hasProvider} = require('oembed-parser');
4
5
  const cheerio = require('cheerio');
5
6
  const _ = require('lodash');
@@ -62,6 +63,7 @@ class OEmbed {
62
63
  */
63
64
  constructor({config, externalRequest}) {
64
65
  this.config = config;
66
+
65
67
  /** @type {IExternalRequest} */
66
68
  this.externalRequest = async (url, requestConfig) => {
67
69
  if (this.isIpOrLocalhost(url)) {
@@ -73,6 +75,7 @@ class OEmbed {
73
75
  }
74
76
  return response;
75
77
  };
78
+
76
79
  /** @type {ICustomProvider[]} */
77
80
  this.customProviders = [];
78
81
  }
@@ -107,24 +110,6 @@ class OEmbed {
107
110
  }
108
111
  }
109
112
 
110
- /**
111
- * @param {string} url
112
- */
113
- errorHandler(url) {
114
- /**
115
- * @param {Error|errors.GhostError} err
116
- */
117
- return async (err) => {
118
- // allow specific validation errors through for better error messages
119
- if (errors.utils.isIgnitionError(err) && err.errorType === 'ValidationError') {
120
- throw err;
121
- }
122
-
123
- // default to unknown provider to avoid leaking any app specifics
124
- return this.unknownProvider(url);
125
- };
126
- }
127
-
128
113
  async fetchBookmarkData(url) {
129
114
  const metascraper = require('metascraper')([
130
115
  require('metascraper-url')(),
@@ -296,10 +281,13 @@ class OEmbed {
296
281
  * @returns {Promise<Object>}
297
282
  */
298
283
  async fetchOembedDataFromUrl(url, type) {
299
- let data;
300
-
301
284
  try {
302
285
  const urlObject = new URL(url);
286
+
287
+ // Trimming solves the difference of url validation between `new URL(url)`
288
+ // and metascraper.
289
+ url = url.trim();
290
+
303
291
  for (const provider of this.customProviders) {
304
292
  if (await provider.canSupportRequest(urlObject)) {
305
293
  const result = await provider.getOEmbedData(urlObject, this.externalRequest);
@@ -309,23 +297,39 @@ class OEmbed {
309
297
  }
310
298
  }
311
299
 
300
+ // fetch only bookmark when explicitly requested
312
301
  if (type === 'bookmark') {
313
302
  return this.fetchBookmarkData(url);
314
303
  }
315
304
 
316
- data = await this.fetchOembedData(url);
305
+ // attempt to fetch oembed
306
+ let data = await this.fetchOembedData(url);
317
307
 
308
+ // fallback to bookmark when we can't get oembed
318
309
  if (!data && !type) {
319
310
  data = await this.fetchBookmarkData(url);
320
311
  }
321
312
 
313
+ // couldn't get anything, throw a validation error
322
314
  if (!data) {
323
- data = await this.unknownProvider(url);
315
+ return this.unknownProvider(url);
324
316
  }
325
317
 
326
318
  return data;
327
- } catch (e) {
328
- return this.errorHandler(url);
319
+ } catch (err) {
320
+ // allow specific validation errors through for better error messages
321
+ if (errors.utils.isGhostError(err) && err.errorType === 'ValidationError') {
322
+ throw err;
323
+ }
324
+
325
+ // log the real error because we're going to throw a generic "Unknown provider" error
326
+ logging.error(new errors.InternalServerError({
327
+ message: 'Encountered error when fetching oembed',
328
+ err
329
+ }));
330
+
331
+ // default to unknown provider to avoid leaking any app specifics
332
+ return this.unknownProvider(url);
329
333
  }
330
334
  }
331
335
  }
@@ -128,7 +128,7 @@ CanThisResult.prototype.beginCheck = function (context) {
128
128
  context = parseContext(context);
129
129
 
130
130
  if (actionsMap.empty()) {
131
- throw new errors.GhostError({message: tpl(messages.noActionsMapFoundError)});
131
+ throw new errors.InternalServerError({message: tpl(messages.noActionsMapFoundError)});
132
132
  }
133
133
 
134
134
  // Kick off loading of user permissions if necessary
@@ -17,7 +17,7 @@ module.exports = function getConfigProperties() {
17
17
  mailgunIsConfigured: config.get('bulkEmail') && config.get('bulkEmail').mailgun,
18
18
  emailAnalytics: config.get('emailAnalytics'),
19
19
  hostSettings: config.get('hostSettings'),
20
- tenorApiKey: config.get('tenorApiKey')
20
+ tenor: config.get('tenor')
21
21
  };
22
22
 
23
23
  const billingUrl = config.get('hostSettings:billing:enabled') ? config.get('hostSettings:billing:url') : '';