ghost 5.10.1 → 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 (126) hide show
  1. package/components/{tryghost-adapter-manager-5.10.1.tgz → 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-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.10.1.tgz → 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.10.1.tgz → 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.10.1.tgz → 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.10.1.tgz → tryghost-job-manager-5.11.0.tgz} +0 -0
  15. package/components/{tryghost-magic-link-5.10.1.tgz → 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.10.1.tgz → 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-5.10.1.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.10.1.tgz → tryghost-members-ssr-5.11.0.tgz} +0 -0
  28. package/components/{tryghost-members-stripe-service-5.10.1.tgz → 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.10.1.tgz → 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.10.1.tgz → 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.10.1.tgz → 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.10.1.tgz → 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.10.1.tgz → tryghost-version-notifications-data-service-5.11.0.tgz} +0 -0
  44. package/core/built/admin/assets/chunk.143.14589cc066b8120b73e3.js +49 -0
  45. package/core/built/admin/assets/{chunk.174.0364e8abdae8210d8e6d.js → chunk.174.ae492405065373dbe102.js} +1 -1
  46. package/core/built/admin/assets/{chunk.178.bb46965ba0483c3e79ae.js → chunk.178.131e85a10d2031148425.js} +4 -4
  47. package/core/built/admin/assets/{chunk.351.ea4a4ff4b40d5f2ad141.js → chunk.579.65e09dd89eec70d059a0.js} +3 -11
  48. package/core/built/admin/assets/{chunk.351.ea4a4ff4b40d5f2ad141.js.LICENSE.txt → chunk.579.65e09dd89eec70d059a0.js.LICENSE.txt} +0 -0
  49. package/core/built/admin/assets/ghost-1b0d7c731511bb738ec457d2932c43c0.css +1 -0
  50. package/core/built/admin/assets/{ghost-ced03a7ac75c3148e0ea7d1bf51e39fc.js → ghost-40f5bd12d121c54bbc39e7939e78244f.js} +745 -566
  51. package/core/built/admin/assets/ghost-dark-7b2825a050b0382630180f48aa78ea5d.css +1 -0
  52. package/core/built/admin/assets/icons/calendar-stroke.svg +1 -0
  53. package/core/built/admin/assets/icons/pen-stroke.svg +1 -0
  54. package/core/built/admin/assets/{vendor-a1ae7a38d5c38fcba5609eed4e37f02a.js → vendor-741dc0e4078e044a0c9bfaad104de8b3.js} +55 -51
  55. package/core/built/admin/index.html +6 -6
  56. package/core/server/api/endpoints/comments-members.js +10 -7
  57. package/core/server/api/endpoints/invites.js +1 -9
  58. package/core/server/api/endpoints/labels.js +1 -7
  59. package/core/server/api/endpoints/members.js +3 -13
  60. package/core/server/api/endpoints/offers.js +2 -2
  61. package/core/server/api/endpoints/pages.js +2 -10
  62. package/core/server/api/endpoints/posts.js +1 -9
  63. package/core/server/api/endpoints/snippets.js +1 -9
  64. package/core/server/api/endpoints/tags.js +1 -7
  65. package/core/server/api/endpoints/utils/serializers/input/pages.js +1 -1
  66. package/core/server/api/endpoints/utils/serializers/output/members.js +2 -1
  67. package/core/server/api/endpoints/utils/serializers/output/site.js +1 -0
  68. package/core/server/api/endpoints/utils/serializers/output/utils/clean.js +6 -7
  69. package/core/server/api/endpoints/webhooks.js +2 -19
  70. package/core/server/data/migrations/versions/5.11/2022-08-22-11-03-add-member-alert-settings-columns-to-users.js +21 -0
  71. package/core/server/data/migrations/versions/5.11/2022-08-23-13-41-backfill-members-created-events.js +32 -0
  72. package/core/server/data/migrations/versions/5.11/2022-08-23-13-59-fix-page-resource-type.js +22 -0
  73. package/core/server/data/schema/fixtures/fixtures.json +3 -0
  74. package/core/server/data/schema/schema.js +3 -0
  75. package/core/server/lib/image/gravatar.js +8 -7
  76. package/core/server/lib/image/image-size.js +60 -56
  77. package/core/server/models/action.js +0 -10
  78. package/core/server/models/base/plugins/actions.js +26 -3
  79. package/core/server/models/member.js +18 -0
  80. package/core/server/models/offer.js +3 -0
  81. package/core/server/models/post.js +2 -3
  82. package/core/server/models/product.js +3 -0
  83. package/core/server/models/settings.js +4 -0
  84. package/core/server/models/user.js +4 -1
  85. package/core/server/services/auth/api-key/admin.js +0 -3
  86. package/core/server/services/auth/passwordreset.js +0 -3
  87. package/core/server/services/explore/service.js +7 -6
  88. package/core/server/services/member-attribution/index.js +34 -6
  89. package/core/server/services/public-config/site.js +1 -0
  90. package/core/server/services/route-settings/default-settings-manager.js +19 -17
  91. package/core/server/services/webhooks/trigger.js +14 -5
  92. package/core/shared/config/defaults.json +3 -2
  93. package/core/shared/labs.js +4 -4
  94. package/package.json +82 -82
  95. package/yarn.lock +92 -109
  96. package/components/tryghost-api-framework-5.10.1.tgz +0 -0
  97. package/components/tryghost-api-version-compatibility-service-5.10.1.tgz +0 -0
  98. package/components/tryghost-bootstrap-socket-5.10.1.tgz +0 -0
  99. package/components/tryghost-constants-5.10.1.tgz +0 -0
  100. package/components/tryghost-custom-theme-settings-service-5.10.1.tgz +0 -0
  101. package/components/tryghost-email-analytics-provider-mailgun-5.10.1.tgz +0 -0
  102. package/components/tryghost-email-content-generator-5.10.1.tgz +0 -0
  103. package/components/tryghost-express-dynamic-redirects-5.10.1.tgz +0 -0
  104. package/components/tryghost-html-to-plaintext-5.10.1.tgz +0 -0
  105. package/components/tryghost-mailgun-client-5.10.1.tgz +0 -0
  106. package/components/tryghost-member-analytics-service-5.10.1.tgz +0 -0
  107. package/components/tryghost-member-attribution-5.10.1.tgz +0 -0
  108. package/components/tryghost-members-analytics-ingress-5.10.1.tgz +0 -0
  109. package/components/tryghost-members-api-5.10.1.tgz +0 -0
  110. package/components/tryghost-members-events-service-5.10.1.tgz +0 -0
  111. package/components/tryghost-members-importer-5.10.1.tgz +0 -0
  112. package/components/tryghost-members-offers-5.10.1.tgz +0 -0
  113. package/components/tryghost-members-payments-5.10.1.tgz +0 -0
  114. package/components/tryghost-minifier-5.10.1.tgz +0 -0
  115. package/components/tryghost-mw-api-version-mismatch-5.10.1.tgz +0 -0
  116. package/components/tryghost-mw-cache-control-5.10.1.tgz +0 -0
  117. package/components/tryghost-mw-error-handler-5.10.1.tgz +0 -0
  118. package/components/tryghost-mw-session-from-token-5.10.1.tgz +0 -0
  119. package/components/tryghost-mw-update-user-last-seen-5.10.1.tgz +0 -0
  120. package/components/tryghost-oembed-service-5.10.1.tgz +0 -0
  121. package/components/tryghost-security-5.10.1.tgz +0 -0
  122. package/components/tryghost-settings-path-manager-5.10.1.tgz +0 -0
  123. package/components/tryghost-verification-trigger-5.10.1.tgz +0 -0
  124. package/core/built/admin/assets/chunk.143.71da0e884297554a8ec4.js +0 -41
  125. package/core/built/admin/assets/ghost-13baab17b3f54b21f341fb8f36f83110.css +0 -1
  126. package/core/built/admin/assets/ghost-dark-b0500577a42e2770994e6aef0e70f182.css +0 -1
@@ -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
  };
@@ -192,15 +192,7 @@ module.exports = {
192
192
  unsafeAttrs: unsafeAttrs
193
193
  },
194
194
  query(frame) {
195
- frame.options.require = true;
196
-
197
- return models.Post.destroy(frame.options)
198
- .then(() => null)
199
- .catch(models.Post.NotFoundError, () => {
200
- return Promise.reject(new errors.NotFoundError({
201
- message: tpl(messages.postNotFound)
202
- }));
203
- });
195
+ return models.Post.destroy({...frame.options, require: true});
204
196
  }
205
197
  }
206
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) {
@@ -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
  };
@@ -0,0 +1,21 @@
1
+ const {createAddColumnMigration, combineNonTransactionalMigrations} = require('../../utils');
2
+
3
+ module.exports = combineNonTransactionalMigrations(
4
+ createAddColumnMigration('users', 'free_member_signup_notification', {
5
+ type: 'boolean',
6
+ nullable: false,
7
+ defaultTo: true
8
+ }),
9
+
10
+ createAddColumnMigration('users', 'paid_subscription_canceled_notification', {
11
+ type: 'boolean',
12
+ nullable: false,
13
+ defaultTo: false
14
+ }),
15
+
16
+ createAddColumnMigration('users', 'paid_subscription_started_notification', {
17
+ type: 'boolean',
18
+ nullable: false,
19
+ defaultTo: true
20
+ })
21
+ );
@@ -0,0 +1,32 @@
1
+ const ObjectID = require('bson-objectid').default;
2
+ const logging = require('@tryghost/logging');
3
+
4
+ const {createTransactionalMigration} = require('../../utils');
5
+
6
+ module.exports = createTransactionalMigration(
7
+ async function up(knex) {
8
+ const members = await knex('members')
9
+ .select('id', 'created_at');
10
+
11
+ if (members.length === 0) {
12
+ logging.warn(`Skipping migration because no members found`);
13
+ return;
14
+ }
15
+
16
+ const toInsert = members.map((member) => {
17
+ return {
18
+ id: ObjectID().toHexString(),
19
+ member_id: member.id,
20
+ created_at: member.created_at,
21
+ source: 'member'
22
+ };
23
+ });
24
+
25
+ logging.info(`Inserting ${toInsert.length} members created events`);
26
+ await knex.batchInsert('members_created_events', toInsert);
27
+ },
28
+ async function down(knex) {
29
+ logging.info(`Clearing all members created events`);
30
+ await knex('members_created_events').del();
31
+ }
32
+ );
@@ -0,0 +1,22 @@
1
+ const logging = require('@tryghost/logging');
2
+ const {createTransactionalMigration} = require('../../utils');
3
+
4
+ module.exports = createTransactionalMigration(
5
+ async function up(knex) {
6
+ logging.info(`Changing Action event 'page' resource_type to 'post'`);
7
+
8
+ const affectedRows = await knex('actions')
9
+ .update({
10
+ resource_type: 'post',
11
+ context: JSON.stringify({
12
+ type: 'page'
13
+ })
14
+ })
15
+ .where('resource_type', 'page');
16
+
17
+ logging.info(`Updated ${affectedRows} Action events from 'page' to 'post'`);
18
+ },
19
+ async function down() {
20
+ // no-op: we don't want to put `pages` back as a resource type
21
+ }
22
+ );
@@ -626,6 +626,9 @@
626
626
  "name": "Ghost",
627
627
  "email": "ghost@example.com",
628
628
  "status": "inactive",
629
+ "free_member_signup_notification": true,
630
+ "paid_subscription_started_notification": true,
631
+ "paid_subscription_canceled_notification": false,
629
632
  "roles": []
630
633
  }
631
634
  ]
@@ -149,6 +149,9 @@ module.exports = {
149
149
  tour: {type: 'text', maxlength: 65535, nullable: true},
150
150
  last_seen: {type: 'dateTime', nullable: true},
151
151
  comment_notifications: {type: 'boolean', nullable: false, defaultTo: true},
152
+ free_member_signup_notification: {type: 'boolean', nullable: false, defaultTo: true},
153
+ paid_subscription_started_notification: {type: 'boolean', nullable: false, defaultTo: true},
154
+ paid_subscription_canceled_notification: {type: 'boolean', nullable: false, defaultTo: false},
152
155
  created_at: {type: 'dateTime', nullable: false},
153
156
  created_by: {type: 'string', maxlength: 24, nullable: false},
154
157
  updated_at: {type: 'dateTime', nullable: true},
@@ -40,15 +40,16 @@ class Gravatar {
40
40
  image: imageUrl
41
41
  };
42
42
  })
43
- .catch({statusCode: 404}, function () {
44
- return {
45
- image: undefined
46
- };
47
- })
48
- .catch(function () {
43
+ .catch(function (err) {
44
+ if (err.statusCode === 404) {
45
+ return {
46
+ image: undefined
47
+ };
48
+ }
49
+
49
50
  // ignore error, just resolve with no image url
50
51
  });
51
52
  }
52
53
  }
53
54
 
54
- module.exports = Gravatar;
55
+ module.exports = Gravatar;
@@ -163,39 +163,41 @@ class ImageSize {
163
163
  width: dimensions.width,
164
164
  height: dimensions.height
165
165
  };
166
- }).catch({code: 'URL_MISSING_INVALID'}, (err) => {
167
- return Promise.reject(new errors.InternalServerError({
168
- message: err.message,
169
- code: 'IMAGE_SIZE_URL',
170
- statusCode: err.statusCode,
171
- context: err.url || imagePath
172
- }));
173
- }).catch({code: 'ETIMEDOUT'}, {code: 'ESOCKETTIMEDOUT'}, {code: 'ECONNRESET'}, {statusCode: 408}, (err) => {
174
- return Promise.reject(new errors.InternalServerError({
175
- message: 'Request timed out.',
176
- code: 'IMAGE_SIZE_URL',
177
- statusCode: err.statusCode,
178
- context: err.url || imagePath
179
- }));
180
- }).catch({code: 'ENOENT'}, {code: 'ENOTFOUND'}, {statusCode: 404}, (err) => {
181
- return Promise.reject(new errors.NotFoundError({
182
- message: 'Image not found.',
183
- code: 'IMAGE_SIZE_URL',
184
- statusCode: err.statusCode,
185
- context: err.url || imagePath
186
- }));
187
- }).catch(function (err) {
188
- if (errors.utils.isGhostError(err)) {
189
- return Promise.reject(err);
190
- }
166
+ }).catch((err) => {
167
+ if (err.code === 'URL_MISSING_INVALID') {
168
+ return Promise.reject(new errors.InternalServerError({
169
+ message: err.message,
170
+ code: 'IMAGE_SIZE_URL',
171
+ statusCode: err.statusCode,
172
+ context: err.url || imagePath
173
+ }));
174
+ } else if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT' || err.code === 'ECONNRESET' || err.statusCode === 408) {
175
+ return Promise.reject(new errors.InternalServerError({
176
+ message: 'Request timed out.',
177
+ code: 'IMAGE_SIZE_URL',
178
+ statusCode: err.statusCode,
179
+ context: err.url || imagePath
180
+ }));
181
+ } else if (err.code === 'ENOENT' || err.code === 'ENOTFOUND' || err.statusCode === 404) {
182
+ return Promise.reject(new errors.NotFoundError({
183
+ message: 'Image not found.',
184
+ code: 'IMAGE_SIZE_URL',
185
+ statusCode: err.statusCode,
186
+ context: err.url || imagePath
187
+ }));
188
+ } else {
189
+ if (errors.utils.isGhostError(err)) {
190
+ return Promise.reject(err);
191
+ }
191
192
 
192
- return Promise.reject(new errors.InternalServerError({
193
- message: 'Unknown Request error.',
194
- code: 'IMAGE_SIZE_URL',
195
- statusCode: err.statusCode,
196
- context: err.url || imagePath,
197
- err: err
198
- }));
193
+ return Promise.reject(new errors.InternalServerError({
194
+ message: 'Unknown Request error.',
195
+ code: 'IMAGE_SIZE_URL',
196
+ statusCode: err.statusCode,
197
+ context: err.url || imagePath,
198
+ err: err
199
+ }));
200
+ }
199
201
  });
200
202
  }
201
203
 
@@ -237,32 +239,34 @@ class ImageSize {
237
239
  height: dimensions.height
238
240
  };
239
241
  })
240
- .catch({code: 'ENOENT'}, (err) => {
241
- return Promise.reject(new errors.NotFoundError({
242
- message: err.message,
243
- code: 'IMAGE_SIZE_STORAGE',
244
- err: err,
245
- context: filePath,
246
- errorDetails: {
247
- originalPath: imagePath,
248
- reqFilePath: filePath
242
+ .catch((err) => {
243
+ if (err.code === 'ENOENT') {
244
+ return Promise.reject(new errors.NotFoundError({
245
+ message: err.message,
246
+ code: 'IMAGE_SIZE_STORAGE',
247
+ err: err,
248
+ context: filePath,
249
+ errorDetails: {
250
+ originalPath: imagePath,
251
+ reqFilePath: filePath
252
+ }
253
+ }));
254
+ } else {
255
+ if (errors.utils.isGhostError(err)) {
256
+ return Promise.reject(err);
249
257
  }
250
- }));
251
- }).catch((err) => {
252
- if (errors.utils.isGhostError(err)) {
253
- return Promise.reject(err);
254
- }
255
258
 
256
- return Promise.reject(new errors.InternalServerError({
257
- message: err.message,
258
- code: 'IMAGE_SIZE_STORAGE',
259
- err: err,
260
- context: filePath,
261
- errorDetails: {
262
- originalPath: imagePath,
263
- reqFilePath: filePath
264
- }
265
- }));
259
+ return Promise.reject(new errors.InternalServerError({
260
+ message: err.message,
261
+ code: 'IMAGE_SIZE_STORAGE',
262
+ err: err,
263
+ context: filePath,
264
+ errorDetails: {
265
+ originalPath: imagePath,
266
+ reqFilePath: filePath
267
+ }
268
+ }));
269
+ }
266
270
  });
267
271
  }
268
272
 
@@ -19,16 +19,6 @@ const Action = ghostBookshelf.Model.extend({
19
19
 
20
20
  resource() {
21
21
  return this.morphTo('resource', ['resource_type', 'resource_id'], ...candidates);
22
- },
23
-
24
- toJSON(unfilteredOptions) {
25
- const options = Action.filterOptions(unfilteredOptions, 'toJSON');
26
- const attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options);
27
-
28
- // @TODO: context is not implemented yet
29
- delete attrs.context;
30
-
31
- return attrs;
32
22
  }
33
23
  }, {
34
24
  orderDefaultOptions: function orderDefaultOptions() {
@@ -29,14 +29,37 @@ module.exports = function (Bookshelf) {
29
29
  resourceType = resourceType.bind(this)();
30
30
  }
31
31
 
32
- // @TODO: implement context
33
- return {
34
- event: event,
32
+ if (!resourceType) {
33
+ return;
34
+ }
35
+
36
+ let context = {};
37
+
38
+ if (this.actionsExtraContext && Array.isArray(this.actionsExtraContext)) {
39
+ for (const c of this.actionsExtraContext) {
40
+ context[c] = this.get(c) || this.previous(c);
41
+ }
42
+ }
43
+
44
+ if (event === 'deleted') {
45
+ context.primary_name = (this.previous('title') || this.previous('name'));
46
+ } else if (['added', 'edited'].includes(event)) {
47
+ context.primary_name = (this.get('title') || this.get('name') || this.previous('title') || this.previous('name'));
48
+ }
49
+
50
+ const data = {
51
+ event,
35
52
  resource_id: this.id || this.previous('id'),
36
53
  resource_type: resourceType,
37
54
  actor_id: actor.id,
38
55
  actor_type: actor.type
39
56
  };
57
+
58
+ if (context && Object.keys(context).length) {
59
+ data.context = context;
60
+ }
61
+
62
+ return data;
40
63
  },
41
64
 
42
65
  /**
@@ -40,6 +40,12 @@ const Member = ghostBookshelf.Model.extend({
40
40
  }, {
41
41
  key: 'newsletters',
42
42
  replacement: 'newsletters.slug'
43
+ }, {
44
+ key: 'signup',
45
+ replacement: 'signups.attribution_id'
46
+ }, {
47
+ key: 'conversion',
48
+ replacement: 'conversions.attribution_id'
43
49
  }];
44
50
  },
45
51
 
@@ -74,6 +80,18 @@ const Member = ghostBookshelf.Model.extend({
74
80
  joinFrom: 'member_id',
75
81
  joinTo: 'customer_id',
76
82
  joinToForeign: 'customer_id'
83
+ },
84
+ signups: {
85
+ tableName: 'members_created_events',
86
+ tableNameAs: 'signups',
87
+ type: 'oneToOne',
88
+ joinFrom: 'member_id'
89
+ },
90
+ conversions: {
91
+ tableName: 'members_subscription_created_events',
92
+ tableNameAs: 'conversions',
93
+ type: 'oneToOne',
94
+ joinFrom: 'member_id'
77
95
  }
78
96
  };
79
97
  },
@@ -3,6 +3,9 @@ const ghostBookshelf = require('./base');
3
3
  const Offer = ghostBookshelf.Model.extend({
4
4
  tableName: 'offers',
5
5
 
6
+ actionsCollectCRUD: true,
7
+ actionsResourceType: 'offer',
8
+
6
9
  product() {
7
10
  return this.belongsTo('Product', 'product_id', 'id');
8
11
  }
@@ -39,9 +39,8 @@ Post = ghostBookshelf.Model.extend({
39
39
  tableName: 'posts',
40
40
 
41
41
  actionsCollectCRUD: true,
42
- actionsResourceType: function () {
43
- return this.get('type') || this.previous('type');
44
- },
42
+ actionsResourceType: 'post',
43
+ actionsExtraContext: ['type'],
45
44
 
46
45
  /**
47
46
  * @NOTE
@@ -4,6 +4,9 @@ const _ = require('lodash');
4
4
  const Product = ghostBookshelf.Model.extend({
5
5
  tableName: 'products',
6
6
 
7
+ actionsCollectCRUD: true,
8
+ actionsResourceType: 'product',
9
+
7
10
  defaults: {
8
11
  active: true,
9
12
  visibility: 'none',
@@ -97,6 +97,10 @@ Settings = ghostBookshelf.Model.extend({
97
97
 
98
98
  tableName: 'settings',
99
99
 
100
+ actionsCollectCRUD: true,
101
+ actionsResourceType: 'setting',
102
+ actionsExtraContext: ['key', 'group'],
103
+
100
104
  emitChange: function emitChange(event, options) {
101
105
  const eventToTrigger = 'settings' + '.' + event;
102
106
  ghostBookshelf.Model.prototype.emitChange.bind(this)(this, eventToTrigger, options);
@@ -65,7 +65,10 @@ User = ghostBookshelf.Model.extend({
65
65
  password: security.identifier.uid(50),
66
66
  visibility: 'public',
67
67
  status: 'active',
68
- comment_notifications: true
68
+ comment_notifications: true,
69
+ free_member_signup_notification: true,
70
+ paid_subscription_started_notification: true,
71
+ paid_subscription_canceled_notification: false
69
72
  };
70
73
  },
71
74
 
@@ -181,9 +181,6 @@ const authenticateWithToken = async (req, res, next, {token, JWT_OPTIONS}) => {
181
181
  );
182
182
 
183
183
  req.user = user;
184
-
185
- next();
186
- return;
187
184
  }
188
185
 
189
186
  // store the api key on the request for later checks and logging