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
@@ -3,6 +3,7 @@ const {createTransactionalMigration} = require('../../utils.js');
3
3
 
4
4
  module.exports = createTransactionalMigration(
5
5
  async function up(knex) {
6
+ // eslint-disable-next-line no-restricted-syntax
6
7
  const compedMemberIds = (await knex('members')
7
8
  .select('members.id')
8
9
  .innerJoin(
@@ -15,6 +15,7 @@ module.exports = createTransactionalMigration(
15
15
  logging.info(`Found ${compedMembers.length} comped members - checking members_status_events`);
16
16
  }
17
17
 
18
+ // eslint-disable-next-line no-restricted-syntax
18
19
  for (const member of compedMembers) {
19
20
  const mostRecentStatusEvent = await knex('members_status_events')
20
21
  .select('*')
@@ -139,7 +139,7 @@ async function hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, trans
139
139
  const client = knex.client.config.client;
140
140
 
141
141
  if (client !== 'sqlite3') {
142
- throw new errors.GhostError({
142
+ throw new errors.InternalServerError({
143
143
  message: tpl(messages.hasForeignSQLite3)
144
144
  });
145
145
  }
@@ -265,7 +265,7 @@ async function hasPrimaryKeySQLite(tableName, transaction) {
265
265
  const client = knex.client.config.client;
266
266
 
267
267
  if (client !== 'sqlite3') {
268
- throw new errors.GhostError({
268
+ throw new errors.InternalServerError({
269
269
  message: tpl(messages.hasPrimaryKeySQLiteError)
270
270
  });
271
271
  }
@@ -0,0 +1,340 @@
1
+ const _ = require('lodash');
2
+ const Promise = require('bluebird');
3
+ const logging = require('@tryghost/logging');
4
+ const {sequence} = require('@tryghost/promise');
5
+
6
+ const models = require('../../../models');
7
+ const baseUtils = require('../../../models/base/utils');
8
+
9
+ const moment = require('moment');
10
+
11
+ class FixtureManager {
12
+ constructor(fixtures) {
13
+ this.fixtures = fixtures;
14
+ }
15
+
16
+ /**
17
+ * ### Match Func
18
+ * Figures out how to match across various combinations of keys and values.
19
+ * Match can be a string or an array containing 2 strings
20
+ * Key and Value are the values to be found
21
+ * Value can also be an array, in which case we look for a match in the array.
22
+ * @api private
23
+ * @param {String|Array} match
24
+ * @param {String|Integer} key
25
+ * @param {String|Array} [value]
26
+ * @returns {Function} matching function
27
+ */
28
+ static matchFunc(match, key, value) {
29
+ if (_.isArray(match)) {
30
+ return function (item) {
31
+ let valueTest = true;
32
+
33
+ if (_.isArray(value)) {
34
+ valueTest = value.indexOf(item.get(match[1])) > -1;
35
+ } else if (value !== 'all') {
36
+ valueTest = item.get(match[1]) === value;
37
+ }
38
+
39
+ return item.get(match[0]) === key && valueTest;
40
+ };
41
+ }
42
+
43
+ return function (item) {
44
+ key = key === 0 && value ? value : key;
45
+ return item.get(match) === key;
46
+ };
47
+ }
48
+
49
+ static matchObj(match, item) {
50
+ const matchedObj = {};
51
+
52
+ if (_.isArray(match)) {
53
+ _.each(match, (matchProp) => {
54
+ matchedObj[matchProp] = item.get(matchProp);
55
+ });
56
+ } else {
57
+ matchedObj[match] = item.get(match);
58
+ }
59
+
60
+ return matchedObj;
61
+ }
62
+
63
+ /**
64
+ * Add All Fixtures
65
+ *
66
+ * Helper method to handle adding all fixtures
67
+ *
68
+ * @param {object} options
69
+ * @returns
70
+ */
71
+ async addAllFixtures(options) {
72
+ const localOptions = _.merge({
73
+ context: {internal: true},
74
+ migrating: true
75
+ }, options);
76
+
77
+ await Promise.mapSeries(this.fixtures.models, (model) => {
78
+ logging.info('Model: ' + model.name);
79
+
80
+ return this.addFixturesForModel(model, localOptions);
81
+ });
82
+
83
+ await Promise.mapSeries(this.fixtures.relations, (relation) => {
84
+ logging.info('Relation: ' + relation.from.model + ' to ' + relation.to.model);
85
+ return this.addFixturesForRelation(relation, localOptions);
86
+ });
87
+ }
88
+
89
+ /*
90
+ * Find methods - use the local fixtures
91
+ */
92
+
93
+ /**
94
+ * ### Find Model Fixture
95
+ * Finds a model fixture based on model name
96
+ * @api private
97
+ * @param {String} modelName
98
+ * @returns {Object} model fixture
99
+ */
100
+ findModelFixture(modelName) {
101
+ return _.find(this.fixtures.models, (modelFixture) => {
102
+ return modelFixture.name === modelName;
103
+ });
104
+ }
105
+
106
+ /**
107
+ * ### Find Model Fixture Entry
108
+ * Find a single model fixture entry by model name & a matching expression for the FIND function
109
+ * @param {String} modelName
110
+ * @param {String|Object|Function} matchExpr
111
+ * @returns {Object} model fixture entry
112
+ */
113
+ findModelFixtureEntry(modelName, matchExpr) {
114
+ return _.find(this.findModelFixture(modelName).entries, matchExpr);
115
+ }
116
+
117
+ /**
118
+ * ### Find Model Fixtures
119
+ * Find a model fixture name & a matching expression for the FILTER function
120
+ * @param {String} modelName
121
+ * @param {String|Object|Function} matchExpr
122
+ * @returns {Object} model fixture
123
+ */
124
+ findModelFixtures(modelName, matchExpr) {
125
+ const foundModel = _.cloneDeep(this.findModelFixture(modelName));
126
+ foundModel.entries = _.filter(foundModel.entries, matchExpr);
127
+ return foundModel;
128
+ }
129
+
130
+ /**
131
+ * ### Find Relation Fixture
132
+ * Find a relation fixture by from & to models
133
+ * @api private
134
+ * @param {String} from
135
+ * @param {String} to
136
+ * @returns {Object} relation fixture
137
+ */
138
+ findRelationFixture(from, to) {
139
+ return _.find(this.fixtures.relations, (relation) => {
140
+ return relation.from.model === from && relation.to.model === to;
141
+ });
142
+ }
143
+
144
+ /**
145
+ * ### Find Permission Relations For Object
146
+ * Specialist function can return the permission relation fixture with only entries for a particular object.model
147
+ * @param {String} objName
148
+ * @returns {Object} fixture relation
149
+ */
150
+ findPermissionRelationsForObject(objName, role) {
151
+ // Make a copy and delete any entries we don't want
152
+ const foundRelation = _.cloneDeep(this.findRelationFixture('Role', 'Permission'));
153
+
154
+ _.each(foundRelation.entries, (entry, key) => {
155
+ _.each(entry, (perm, obj) => {
156
+ if (obj !== objName) {
157
+ delete entry[obj];
158
+ }
159
+ });
160
+
161
+ if (_.isEmpty(entry) || (role && role !== key)) {
162
+ delete foundRelation.entries[key];
163
+ }
164
+ });
165
+
166
+ return foundRelation;
167
+ }
168
+
169
+ /******************************************************
170
+ * From here down, the methods require access to models
171
+ * But aren't dependent on this.fixtures
172
+ ******************************************************/
173
+
174
+ /**
175
+ * ### Fetch Relation Data
176
+ * Before we build relations we need to fetch all of the models from both sides so that we can
177
+ * use filter and find to quickly locate the correct models.
178
+ * @api private
179
+ * @param {{from, to, entries}} relation
180
+ * @returns {Promise<*>}
181
+ */
182
+ fetchRelationData(relation, options) {
183
+ const fromOptions = _.extend({}, options, {withRelated: [relation.from.relation]});
184
+
185
+ const props = {
186
+ from: models[relation.from.model].findAll(fromOptions),
187
+ to: models[relation.to.model].findAll(options)
188
+ };
189
+
190
+ return Promise.props(props);
191
+ }
192
+
193
+ /**
194
+ * ### Add Fixtures for Model
195
+ * Takes a model fixture, with a name and some entries and processes these
196
+ * into a sequence of promises to get each fixture added.
197
+ *
198
+ * @param {{name, entries}} modelFixture
199
+ * @returns {Promise<any>}
200
+ */
201
+ async addFixturesForModel(modelFixture, options = {}) {
202
+ // Clone the fixtures as they get changed in this function.
203
+ // The initial blog posts will be added a `published_at` property, which
204
+ // would change the fixturesHash.
205
+ modelFixture = _.cloneDeep(modelFixture);
206
+ // The Post model fixtures need a `published_at` date, where at least the seconds
207
+ // are different, otherwise `prev_post` and `next_post` helpers won't workd with
208
+ // them.
209
+ if (modelFixture.name === 'Post') {
210
+ _.forEach(modelFixture.entries, (post, index) => {
211
+ if (!post.published_at) {
212
+ post.published_at = moment().add(index, 'seconds');
213
+ }
214
+ });
215
+ }
216
+
217
+ const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
218
+ let data = {};
219
+
220
+ // CASE: if id is specified, only query by id
221
+ if (entry.id) {
222
+ data.id = entry.id;
223
+ } else if (entry.slug) {
224
+ data.slug = entry.slug;
225
+ } else {
226
+ data = _.cloneDeep(entry);
227
+ }
228
+
229
+ if (modelFixture.name === 'Post') {
230
+ data.status = 'all';
231
+ }
232
+
233
+ const found = await models[modelFixture.name].findOne(data, options);
234
+ if (!found) {
235
+ return models[modelFixture.name].add(entry, options);
236
+ }
237
+ });
238
+
239
+ return {expected: modelFixture.entries.length, done: _.compact(results).length};
240
+ }
241
+
242
+ /**
243
+ * ## Add Fixtures for Relation
244
+ * Takes a relation fixtures object, with a from, to and some entries and processes these
245
+ * into a sequence of promises, to get each fixture added.
246
+ *
247
+ * @param {{from, to, entries}} relationFixture
248
+ * @returns {Promise<any>}
249
+ */
250
+ async addFixturesForRelation(relationFixture, options) {
251
+ const ops = [];
252
+ let max = 0;
253
+
254
+ const data = await this.fetchRelationData(relationFixture, options);
255
+
256
+ _.each(relationFixture.entries, (entry, key) => {
257
+ const fromItem = data.from.find(FixtureManager.matchFunc(relationFixture.from.match, key));
258
+
259
+ // CASE: You add new fixtures e.g. a new role in a new release.
260
+ // As soon as an **older** migration script wants to add permissions for any resource, it iterates over the
261
+ // permissions for each role. But if the role does not exist yet, it won't find the matching db entry and breaks.
262
+ if (!fromItem) {
263
+ logging.warn('Skip: Target database entry not found for key: ' + key);
264
+ return Promise.resolve();
265
+ }
266
+
267
+ _.each(entry, (value, entryKey) => {
268
+ let toItems = data.to.filter(FixtureManager.matchFunc(relationFixture.to.match, entryKey, value));
269
+ max += toItems.length;
270
+
271
+ // Remove any duplicates that already exist in the collection
272
+ toItems = _.reject(toItems, (item) => {
273
+ return fromItem
274
+ .related(relationFixture.from.relation)
275
+ .find((model) => {
276
+ const objectToMatch = FixtureManager.matchObj(relationFixture.to.match, item);
277
+ return Object.keys(objectToMatch).every((keyToCheck) => {
278
+ return model.get(keyToCheck) === objectToMatch[keyToCheck];
279
+ });
280
+ });
281
+ });
282
+
283
+ if (toItems && toItems.length > 0) {
284
+ ops.push(function addRelationItems() {
285
+ return baseUtils.attach(
286
+ models[relationFixture.from.Model || relationFixture.from.model],
287
+ fromItem.id,
288
+ relationFixture.from.relation,
289
+ toItems,
290
+ options
291
+ );
292
+ });
293
+ }
294
+ });
295
+ });
296
+
297
+ const result = await sequence(ops);
298
+ return {expected: max, done: _(result).map('length').sum()};
299
+ }
300
+
301
+ async removeFixturesForModel(modelFixture, options) {
302
+ const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
303
+ const found = models[modelFixture.name].findOne(entry.id ? {id: entry.id} : entry, options);
304
+ if (found) {
305
+ return models[modelFixture.name].destroy(_.extend(options, {id: found.id}));
306
+ }
307
+ });
308
+
309
+ return {expected: modelFixture.entries.length, done: results.length};
310
+ }
311
+
312
+ async removeFixturesForRelation(relationFixture, options) {
313
+ const data = await this.fetchRelationData(relationFixture, options);
314
+ const ops = [];
315
+
316
+ _.each(relationFixture.entries, (entry, key) => {
317
+ const fromItem = data.from.find(FixtureManager.matchFunc(relationFixture.from.match, key));
318
+
319
+ _.each(entry, (value, entryKey) => {
320
+ const toItems = data.to.filter(FixtureManager.matchFunc(relationFixture.to.match, entryKey, value));
321
+
322
+ if (toItems && toItems.length > 0) {
323
+ ops.push(function detachRelation() {
324
+ return baseUtils.detach(
325
+ models[relationFixture.from.Model || relationFixture.from.model],
326
+ fromItem.id,
327
+ relationFixture.from.relation,
328
+ toItems,
329
+ options
330
+ );
331
+ });
332
+ }
333
+ });
334
+ });
335
+
336
+ return await sequence(ops);
337
+ }
338
+ }
339
+
340
+ module.exports = FixtureManager;
@@ -1,2 +1,8 @@
1
- module.exports = require('./fixtures');
2
- module.exports.utils = require('./utils');
1
+ const FixtureManager = require('./fixture-manager');
2
+ const config = require('../../../../shared/config');
3
+
4
+ const fixturePath = config.get('paths').fixtures;
5
+ const fixtures = require(fixturePath);
6
+
7
+ module.exports.FixtureManager = FixtureManager;
8
+ module.exports.fixtureManager = new FixtureManager(fixtures);
@@ -107,13 +107,13 @@ class GhostServer {
107
107
  let ghostError;
108
108
 
109
109
  if (error.code === 'EADDRINUSE') {
110
- ghostError = new errors.GhostError({
110
+ ghostError = new errors.InternalServerError({
111
111
  message: tpl(messages.addressInUse.error),
112
112
  context: tpl(messages.addressInUse.context, {port: config.get('server').port}),
113
113
  help: tpl(messages.addressInUse.help)
114
114
  });
115
115
  } else {
116
- ghostError = new errors.GhostError({
116
+ ghostError = new errors.InternalServerError({
117
117
  message: tpl(messages.otherError.error, {errorNumber: error.errno}),
118
118
  context: tpl(messages.otherError.context),
119
119
  help: tpl(messages.otherError.help)
@@ -177,7 +177,7 @@ class ImageSize {
177
177
  context: err.url || imagePath
178
178
  }));
179
179
  }).catch(function (err) {
180
- if (errors.utils.isIgnitionError(err)) {
180
+ if (errors.utils.isGhostError(err)) {
181
181
  return Promise.reject(err);
182
182
  }
183
183
 
@@ -241,7 +241,7 @@ class ImageSize {
241
241
  }
242
242
  }));
243
243
  }).catch((err) => {
244
- if (errors.utils.isIgnitionError(err)) {
244
+ if (errors.utils.isGhostError(err)) {
245
245
  return Promise.reject(err);
246
246
  }
247
247
 
@@ -69,13 +69,13 @@ events.on('settings.timezone.edited', function (settingModel, options) {
69
69
  try {
70
70
  await models.Post.edit(post.toJSON(), _.merge({id: post.id}, options));
71
71
  } catch (err) {
72
- logging.error(new errors.GhostError({
72
+ logging.error(new errors.InternalServerError({
73
73
  err
74
74
  }));
75
75
  }
76
76
  });
77
77
  } catch (err) {
78
- logging.error(new errors.GhostError({
78
+ logging.error(new errors.InternalServerError({
79
79
  err: err,
80
80
  level: 'critical'
81
81
  }));
@@ -9,11 +9,11 @@ const MemberEmailChangeEvent = ghostBookshelf.Model.extend({
9
9
  }
10
10
  }, {
11
11
  async edit() {
12
- throw new errors.IncorrectUsageError('Cannot edit MemberEmailChangeEvent');
12
+ throw new errors.IncorrectUsageError({message: 'Cannot edit MemberEmailChangeEvent'});
13
13
  },
14
14
 
15
15
  async destroy() {
16
- throw new errors.IncorrectUsageError('Cannot destroy MemberEmailChangeEvent');
16
+ throw new errors.IncorrectUsageError({message: 'Cannot destroy MemberEmailChangeEvent'});
17
17
  }
18
18
  });
19
19
 
@@ -9,11 +9,11 @@ const MemberLoginEvent = ghostBookshelf.Model.extend({
9
9
  }
10
10
  }, {
11
11
  async edit() {
12
- throw new errors.IncorrectUsageError('Cannot edit MemberLoginEvent');
12
+ throw new errors.IncorrectUsageError({message: 'Cannot edit MemberLoginEvent'});
13
13
  },
14
14
 
15
15
  async destroy() {
16
- throw new errors.IncorrectUsageError('Cannot destroy MemberLoginEvent');
16
+ throw new errors.IncorrectUsageError({message: 'Cannot destroy MemberLoginEvent'});
17
17
  }
18
18
  });
19
19
 
@@ -11,7 +11,7 @@ const MemberPaidSubscriptionEvent = ghostBookshelf.Model.extend({
11
11
  customQuery(qb, options) {
12
12
  if (options.aggregateMRRDeltas) {
13
13
  if (options.limit || options.filter) {
14
- throw new errors.IncorrectUsageError('aggregateMRRDeltas does not work when passed a filter or limit');
14
+ throw new errors.IncorrectUsageError({message: 'aggregateMRRDeltas does not work when passed a filter or limit'});
15
15
  }
16
16
  const knex = ghostBookshelf.knex;
17
17
  return qb.clear('select')
@@ -33,11 +33,11 @@ const MemberPaidSubscriptionEvent = ghostBookshelf.Model.extend({
33
33
  return options;
34
34
  },
35
35
  async edit() {
36
- throw new errors.IncorrectUsageError('Cannot edit MemberPaidSubscriptionEvent');
36
+ throw new errors.IncorrectUsageError({message: 'Cannot edit MemberPaidSubscriptionEvent'});
37
37
  },
38
38
 
39
39
  async destroy() {
40
- throw new errors.IncorrectUsageError('Cannot destroy MemberPaidSubscriptionEvent');
40
+ throw new errors.IncorrectUsageError({message: 'Cannot destroy MemberPaidSubscriptionEvent'});
41
41
  }
42
42
  });
43
43
 
@@ -11,7 +11,7 @@ const MemberPaymentEvent = ghostBookshelf.Model.extend({
11
11
  customQuery(qb, options) {
12
12
  if (options.aggregatePaymentVolume) {
13
13
  if (options.limit || options.filter) {
14
- throw new errors.IncorrectUsageError('aggregatePaymentVolume does not work when passed a filter or limit');
14
+ throw new errors.IncorrectUsageError({message: 'aggregatePaymentVolume does not work when passed a filter or limit'});
15
15
  }
16
16
  const knex = ghostBookshelf.knex;
17
17
  return qb.clear('select')
@@ -33,11 +33,11 @@ const MemberPaymentEvent = ghostBookshelf.Model.extend({
33
33
  return options;
34
34
  },
35
35
  async edit() {
36
- throw new errors.IncorrectUsageError('Cannot edit MemberPaymentEvent');
36
+ throw new errors.IncorrectUsageError({message: 'Cannot edit MemberPaymentEvent'});
37
37
  },
38
38
 
39
39
  async destroy() {
40
- throw new errors.IncorrectUsageError('Cannot destroy MemberPaymentEvent');
40
+ throw new errors.IncorrectUsageError({message: 'Cannot destroy MemberPaymentEvent'});
41
41
  }
42
42
  });
43
43
 
@@ -19,15 +19,15 @@ const MemberProductEvent = ghostBookshelf.Model.extend({
19
19
 
20
20
  }, {
21
21
  async edit() {
22
- throw new errors.IncorrectUsageError(
23
- tpl(messages.cannotPerformAction, 'edit')
24
- );
22
+ throw new errors.IncorrectUsageError({
23
+ message: tpl(messages.cannotPerformAction, 'edit')
24
+ });
25
25
  },
26
26
 
27
27
  async destroy() {
28
- throw new errors.IncorrectUsageError(
29
- tpl(messages.cannotPerformAction, 'destroy')
30
- );
28
+ throw new errors.IncorrectUsageError({
29
+ message: tpl(messages.cannotPerformAction, 'destroy')
30
+ });
31
31
  }
32
32
  });
33
33
 
@@ -11,7 +11,9 @@ const MemberStatusEvent = ghostBookshelf.Model.extend({
11
11
  customQuery(qb, options) {
12
12
  if (options.aggregateStatusCounts) {
13
13
  if (options.limit || options.filter) {
14
- throw new errors.IncorrectUsageError('aggregateStatusCounts does not work when passed a filter or limit');
14
+ throw new errors.IncorrectUsageError({
15
+ message: 'aggregateStatusCounts does not work when passed a filter or limit'
16
+ });
15
17
  }
16
18
  const knex = ghostBookshelf.knex;
17
19
  return qb.clear('select')
@@ -46,11 +48,11 @@ const MemberStatusEvent = ghostBookshelf.Model.extend({
46
48
  return options;
47
49
  },
48
50
  async edit() {
49
- throw new errors.IncorrectUsageError('Cannot edit MemberStatusEvent');
51
+ throw new errors.IncorrectUsageError({message: 'Cannot edit MemberStatusEvent'});
50
52
  },
51
53
 
52
54
  async destroy() {
53
- throw new errors.IncorrectUsageError('Cannot destroy MemberStatusEvent');
55
+ throw new errors.IncorrectUsageError({message: 'Cannot destroy MemberStatusEvent'});
54
56
  }
55
57
  });
56
58
 
@@ -11,7 +11,9 @@ const MemberSubscribeEvent = ghostBookshelf.Model.extend({
11
11
  customQuery(qb, options) {
12
12
  if (options.aggregateSubscriptionDeltas) {
13
13
  if (options.limit || options.filter) {
14
- throw new errors.IncorrectUsageError('aggregateSubscriptionDeltas does not work when passed a filter or limit');
14
+ throw new errors.IncorrectUsageError({
15
+ message: 'aggregateSubscriptionDeltas does not work when passed a filter or limit'
16
+ });
15
17
  }
16
18
  const knex = ghostBookshelf.knex;
17
19
  return qb.clear('select')
@@ -32,11 +34,15 @@ const MemberSubscribeEvent = ghostBookshelf.Model.extend({
32
34
  return options;
33
35
  },
34
36
  async edit() {
35
- throw new errors.IncorrectUsageError('Cannot edit MemberSubscribeEvent');
37
+ throw new errors.IncorrectUsageError({
38
+ message: 'Cannot edit MemberSubscribeEvent'
39
+ });
36
40
  },
37
41
 
38
42
  async destroy() {
39
- throw new errors.IncorrectUsageError('Cannot destroy MemberSubscribeEvent');
43
+ throw new errors.IncorrectUsageError({
44
+ message: 'Cannot destroy MemberSubscribeEvent'
45
+ });
40
46
  }
41
47
  });
42
48
 
@@ -324,7 +324,7 @@ module.exports.extendModel = function extendModel(Post, Posts, ghostBookshelf) {
324
324
  .then(() => response);
325
325
  })
326
326
  .catch((err) => {
327
- throw new errors.GhostError({err: err});
327
+ throw new errors.InternalServerError({err: err});
328
328
  });
329
329
  });
330
330
 
@@ -346,7 +346,7 @@ Settings = ghostBookshelf.Model.extend({
346
346
  );
347
347
 
348
348
  if (validationErrors.length) {
349
- throw new errors.ValidationError(validationErrors.join('\n'));
349
+ throw new errors.ValidationError({message: validationErrors.join('\n')});
350
350
  }
351
351
  },
352
352
  async labs(model) {
@@ -148,7 +148,7 @@ function doReset(options, tokenParts, settingsAPI) {
148
148
  return Promise.reject(err);
149
149
  })
150
150
  .catch((err) => {
151
- if (errors.utils.isIgnitionError(err)) {
151
+ if (errors.utils.isGhostError(err)) {
152
152
  return Promise.reject(err);
153
153
  }
154
154
  return Promise.reject(new errors.UnauthorizedError({err: err}));
@@ -59,7 +59,7 @@ async function setupUser(userData) {
59
59
  const owner = await models.User.findOne({role: 'Owner', status: 'all'});
60
60
 
61
61
  if (!owner) {
62
- throw new errors.GhostError({
62
+ throw new errors.InternalServerError({
63
63
  message: tpl(messages.setupUnableToRun)
64
64
  });
65
65
  }
@@ -12,7 +12,7 @@ module.exports = {
12
12
  !hasScheduled &&
13
13
  config.get('emailAnalytics') &&
14
14
  config.get('backgroundJobs:emailAnalytics') &&
15
- !process.env.NODE_ENV.match(/^testing/)
15
+ !process.env.NODE_ENV.startsWith('test')
16
16
  ) {
17
17
  // Don't register email analytics job if we have no emails,
18
18
  // processor usage from many sites spinning up threads can be high.