ghost 4.19.1 → 4.20.3

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 (139) hide show
  1. package/.eslintrc.js +9 -8
  2. package/Gruntfile.js +1 -1
  3. package/PRIVACY.md +3 -0
  4. package/content/adapters/README.md +2 -2
  5. package/core/boot.js +4 -4
  6. package/core/bridge.js +9 -1
  7. package/core/built/assets/{chunk.3.0778d8e4d707d2a625f1.js → chunk.3.777d43e2ce954ba8b2f5.js} +1 -1
  8. package/core/built/assets/codemirror/{codemirror-21a09582262987037db73b152fb35f7c.js → codemirror-d25c379b87ec8b33d54ac7149bc0b6ae.js} +14 -14
  9. package/core/built/assets/ghost-dark-20e2892d4f30d0d1183c9ac725ea37d0.css +1 -0
  10. package/core/built/assets/{ghost.min-102753ec485602c8fe80d60a1750bf84.js → ghost.min-07b6a50c54b3e2e190332c28c7255d2f.js} +525 -340
  11. package/core/built/assets/ghost.min-57e46fd3b1145ecf2cbd185a13611f3b.css +1 -0
  12. package/core/built/assets/icons/arrow-left-small.svg +0 -4
  13. package/core/built/assets/img/footer-marketplace-bg-572b6c6486a7e26316954d599eaa9f30.png +0 -0
  14. package/core/built/assets/img/marketing/offers-1-f2e1b653c4d5bb90eea9d7a2862530f9.jpg +0 -0
  15. package/core/built/assets/img/marketing/offers-2-28a225d34cc39d133748431536961d00.jpg +0 -0
  16. package/core/built/assets/img/marketing/offers-3-2094c91ab21a16c37fbe6ec16c140160.jpg +0 -0
  17. package/core/built/assets/img/themes/Casper-c7e784d7188cc5d7f097d9b6c97b0263.jpg +0 -0
  18. package/core/built/assets/simplemde/{simplemde-232f69d126310434489071a1891e6d8b.js → simplemde-3ffc0ec9e9fecf29b9a499db678c9e65.js} +14 -14
  19. package/core/built/assets/{vendor.min-0916203b598271a795909e8e0b1c16c2.js → vendor.min-af502ac4142871500fc424f6a5a254ec.js} +1046 -1043
  20. package/core/frontend/apps/amp/lib/router.js +1 -1
  21. package/core/frontend/meta/author-url.js +1 -1
  22. package/core/frontend/meta/url.js +1 -1
  23. package/core/{server → frontend}/public/favicon.ico +0 -0
  24. package/core/{server → frontend}/public/ghost.css +0 -0
  25. package/core/{server → frontend}/public/ghost.min.css +0 -0
  26. package/core/{server → frontend}/public/robots.txt +0 -0
  27. package/core/{server → frontend}/public/sitemap.xsl +0 -0
  28. package/core/frontend/services/proxy.js +1 -1
  29. package/core/frontend/services/routing/CollectionRouter.js +3 -49
  30. package/core/frontend/services/routing/ParentRouter.js +1 -4
  31. package/core/frontend/services/routing/StaticPagesRouter.js +3 -5
  32. package/core/frontend/services/routing/StaticRoutesRouter.js +4 -6
  33. package/core/frontend/services/routing/TaxonomyRouter.js +4 -5
  34. package/core/frontend/services/routing/controllers/collection.js +2 -2
  35. package/core/frontend/services/routing/controllers/email-post.js +2 -2
  36. package/core/frontend/services/routing/controllers/entry.js +2 -2
  37. package/core/frontend/services/routing/controllers/preview.js +2 -2
  38. package/core/frontend/services/routing/index.js +6 -12
  39. package/core/frontend/services/routing/registry.js +13 -0
  40. package/core/frontend/services/routing/router-manager.js +185 -0
  41. package/core/frontend/services/rss/generate-feed.js +2 -2
  42. package/core/frontend/services/theme-engine/i18n/i18n.js +267 -28
  43. package/core/frontend/services/theme-engine/i18n/index.js +1 -1
  44. package/core/frontend/services/theme-engine/i18n/theme-i18n.js +73 -0
  45. package/core/frontend/web/index.js +1 -0
  46. package/core/{server/web/site → frontend/web}/middleware/handle-image-sizes.js +4 -4
  47. package/core/{server/web/site → frontend/web}/middleware/index.js +0 -0
  48. package/core/{server/web/site → frontend/web}/middleware/redirect-ghost-to-admin.js +3 -3
  49. package/core/{server/web/site → frontend/web}/middleware/serve-favicon.js +6 -6
  50. package/core/{server/web/site → frontend/web}/middleware/serve-public-file.js +2 -2
  51. package/core/{server/web/site → frontend/web}/middleware/static-theme.js +3 -3
  52. package/core/{server/web/site → frontend/web}/routes.js +5 -4
  53. package/core/{server/web/site/app.js → frontend/web/site.js} +12 -16
  54. package/core/server/adapters/storage/LocalFileStorage.js +35 -39
  55. package/core/server/adapters/storage/index.js +12 -2
  56. package/core/server/api/canary/images.js +1 -1
  57. package/core/server/api/canary/offers.js +19 -0
  58. package/core/server/api/canary/utils/serializers/output/settings.js +2 -3
  59. package/core/server/api/canary/utils/serializers/output/utils/url.js +1 -1
  60. package/core/server/api/v2/images.js +1 -1
  61. package/core/server/api/v2/utils/serializers/output/utils/url.js +1 -1
  62. package/core/server/api/v3/images.js +1 -1
  63. package/core/server/api/v3/utils/serializers/output/settings.js +2 -3
  64. package/core/server/api/v3/utils/serializers/output/utils/url.js +1 -1
  65. package/core/server/data/importer/handlers/image.js +1 -1
  66. package/core/server/data/importer/importers/image.js +1 -1
  67. package/core/server/data/migrations/init/1-create-tables.js +7 -8
  68. package/core/server/data/migrations/init/2-create-fixtures.js +8 -8
  69. package/core/server/data/migrations/versions/4.20/01-remove-offer-redemptions-table.js +19 -0
  70. package/core/server/data/migrations/versions/4.20/02-remove-offers-table.js +30 -0
  71. package/core/server/data/migrations/versions/4.20/03-add-offers-table.js +21 -0
  72. package/core/server/data/migrations/versions/4.20/04-add-offer-redemptions-table.js +9 -0
  73. package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +41 -0
  74. package/core/server/data/schema/fixtures/utils.js +150 -143
  75. package/core/server/data/schema/schema.js +4 -3
  76. package/core/server/frontend/ghost.min.css +1 -0
  77. package/core/server/lib/image/image-size.js +2 -2
  78. package/core/server/lib/mobiledoc.js +3 -2
  79. package/core/server/models/action.js +7 -4
  80. package/core/server/models/base/plugins/overrides.js +19 -6
  81. package/core/server/models/index.js +4 -46
  82. package/core/server/models/member.js +5 -0
  83. package/core/server/models/user.js +2 -1
  84. package/core/server/overrides.js +6 -2
  85. package/core/server/services/adapter-manager/config.js +1 -0
  86. package/core/server/services/adapter-manager/index.js +9 -5
  87. package/core/server/services/adapter-manager/options-resolver.js +18 -0
  88. package/core/server/services/bulk-email/mailgun.js +1 -1
  89. package/core/server/services/mega/post-email-serializer.js +2 -2
  90. package/core/server/services/members/api.js +1 -3
  91. package/core/server/services/members/emails/signin.js +1 -1
  92. package/core/server/services/members/emails/signup.js +1 -1
  93. package/core/server/services/members/emails/subscribe.js +1 -1
  94. package/core/server/services/members/service.js +2 -1
  95. package/core/server/services/offers/service.js +1 -1
  96. package/core/server/services/route-settings/route-settings.js +1 -1
  97. package/core/server/services/settings/index.js +3 -1
  98. package/core/server/services/settings/settings-bread-service.js +42 -20
  99. package/core/server/services/slack.js +1 -1
  100. package/core/server/services/themes/activate.js +2 -2
  101. package/core/server/services/themes/activation-bridge.js +6 -6
  102. package/core/server/services/themes/storage.js +1 -1
  103. package/core/{frontend → server}/services/url/Queue.js +0 -0
  104. package/core/{frontend → server}/services/url/Resource.js +0 -0
  105. package/core/{frontend → server}/services/url/Resources.js +2 -2
  106. package/core/{frontend → server}/services/url/UrlGenerator.js +14 -14
  107. package/core/{frontend → server}/services/url/UrlService.js +12 -15
  108. package/core/{frontend → server}/services/url/Urls.js +1 -1
  109. package/core/{frontend → server}/services/url/configs/canary.js +0 -0
  110. package/core/{frontend → server}/services/url/configs/v2.js +0 -0
  111. package/core/{frontend → server}/services/url/configs/v3.js +0 -0
  112. package/core/{frontend → server}/services/url/configs/v4.js +0 -0
  113. package/core/{frontend → server}/services/url/index.js +0 -0
  114. package/core/server/services/xmlrpc.js +1 -1
  115. package/core/server/update-check.js +3 -3
  116. package/core/server/web/admin/controller.js +11 -0
  117. package/core/server/web/admin/views/default-prod.html +4 -4
  118. package/core/server/web/admin/views/default.html +4 -4
  119. package/core/server/web/api/app.js +8 -9
  120. package/core/server/web/oauth/app.js +4 -2
  121. package/core/server/web/parent/backend.js +3 -3
  122. package/core/server/web/parent/frontend.js +2 -2
  123. package/core/server/web/shared/middlewares/custom-redirects.js +0 -8
  124. package/core/server/web/shared/middlewares/maintenance.js +1 -1
  125. package/core/server/web/well-known.js +10 -10
  126. package/core/shared/config/overrides.json +1 -1
  127. package/core/shared/express.js +10 -0
  128. package/core/shared/html-to-plaintext.js +2 -2
  129. package/core/shared/labs.js +14 -5
  130. package/package.json +45 -43
  131. package/yarn.lock +649 -284
  132. package/core/built/assets/ghost-dark-da8e8eba130fb52f97494e51850d1045.css +0 -1
  133. package/core/built/assets/ghost.min-0d8f19623e9f077351bce453034daf4d.css +0 -1
  134. package/core/frontend/services/routing/bootstrap.js +0 -134
  135. package/core/server/public/404-ghost.png +0 -0
  136. package/core/server/public/404-ghost@2x.png +0 -0
  137. package/core/server/web/site/index.js +0 -1
  138. package/core/shared/i18n/i18n.js +0 -312
  139. package/core/shared/i18n/index.js +0 -6
@@ -6,7 +6,7 @@ const tpl = require('@tryghost/tpl');
6
6
  const errors = require('@tryghost/errors');
7
7
 
8
8
  // Dirty requires
9
- const urlService = require('../../../services/url');
9
+ const urlService = require('../../../../server/services/url');
10
10
  const helpers = require('../../../services/routing/helpers');
11
11
  const templateName = 'amp';
12
12
 
@@ -1,4 +1,4 @@
1
- const urlService = require('../services/url');
1
+ const urlService = require('../../server/services/url');
2
2
  const getContextObject = require('./context-object.js');
3
3
 
4
4
  function getAuthorUrl(data, absolute) {
@@ -1,6 +1,6 @@
1
1
  const schema = require('../../server/data/schema').checks;
2
2
  const urlUtils = require('../../shared/url-utils');
3
- const urlService = require('../services/url');
3
+ const urlService = require('../../server/services/url');
4
4
 
5
5
  // This cleans the url from any `/amp` postfixes, so we'll never
6
6
  // output a url with `/amp` in the end, except for the needed `amphtml`
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -46,6 +46,6 @@ module.exports = {
46
46
  // Labs utils for enabling/disabling helpers
47
47
  labs: require('../../shared/labs'),
48
48
  // URGH... Yuk (unhelpful comment :D)
49
- urlService: require('./url'),
49
+ urlService: require('../../server/services/url'),
50
50
  urlUtils: require('../../shared/url-utils')
51
51
  };
@@ -6,16 +6,13 @@ const controllers = require('./controllers');
6
6
  const middlewares = require('./middlewares');
7
7
  const RSSRouter = require('./RSSRouter');
8
8
 
9
- // This emits its own routing events AND listens to settings.timezone.edited :(
10
- const events = require('../../../server/lib/common/events');
11
-
12
9
  /**
13
10
  * @description Collection Router for post resource.
14
11
  *
15
12
  * Fundamental router to define where resources live and how their url structure is.
16
13
  */
17
14
  class CollectionRouter extends ParentRouter {
18
- constructor(mainRoute, object, RESOURCE_CONFIG) {
15
+ constructor(mainRoute, object, RESOURCE_CONFIG, routerCreated) {
19
16
  super('CollectionRouter');
20
17
 
21
18
  this.RESOURCE_CONFIG = RESOURCE_CONFIG.QUERY.post;
@@ -55,11 +52,11 @@ class CollectionRouter extends ParentRouter {
55
52
  };
56
53
 
57
54
  this.context = [this.routerName];
55
+ this.routerCreated = routerCreated;
58
56
 
59
57
  debug(this.name, this.route, this.permalinks);
60
58
 
61
59
  this._registerRoutes();
62
- this._listeners();
63
60
  }
64
61
 
65
62
  /**
@@ -92,7 +89,7 @@ class CollectionRouter extends ParentRouter {
92
89
  // REGISTER: permalinks e.g. /:slug/, /podcast/:slug
93
90
  this.mountRoute(this.permalinks.getValue({withUrlOptions: true}), controllers.entry);
94
91
 
95
- events.emit('router.created', this);
92
+ this.routerCreated(this);
96
93
  }
97
94
 
98
95
  /**
@@ -127,43 +124,6 @@ class CollectionRouter extends ParentRouter {
127
124
  next();
128
125
  }
129
126
 
130
- /**
131
- * @description This router has listeners to react on changes which happen in Ghost.
132
- * @private
133
- */
134
- _listeners() {
135
- /**
136
- * CASE: timezone changes
137
- *
138
- * If your permalink contains a date reference, we have to regenerate the urls.
139
- *
140
- * e.g. /:year/:month/:day/:slug/ or /:day/:slug/
141
- */
142
- this._onTimezoneEditedListener = this._onTimezoneEdited.bind(this);
143
- events.on('settings.timezone.edited', this._onTimezoneEditedListener);
144
- }
145
-
146
- /**
147
- * @description Helper function to handle a timezone change.
148
- * @param settingModel
149
- * @private
150
- */
151
- _onTimezoneEdited(settingModel) {
152
- const newTimezone = settingModel.attributes.value;
153
- const previousTimezone = settingModel._previousAttributes.value;
154
-
155
- if (newTimezone === previousTimezone) {
156
- return;
157
- }
158
-
159
- if (this.getPermalinks().getValue().match(/:year|:month|:day/)) {
160
- debug('_onTimezoneEdited: trigger regeneration');
161
-
162
- // @NOTE: The connected url generator will listen on this event and regenerate urls.
163
- this.emit('updated');
164
- }
165
- }
166
-
167
127
  /**
168
128
  * @description Get resource type of this router (always "posts")
169
129
  * @returns {string}
@@ -196,12 +156,6 @@ class CollectionRouter extends ParentRouter {
196
156
 
197
157
  return urlUtils.createUrl(urlUtils.urlJoin(this.route.value, this.rssRouter.route.value), options.absolute, options.secure);
198
158
  }
199
-
200
- reset() {
201
- if (this._onTimezoneEditedListener) {
202
- events.removeListener('settings.timezone.edited', this._onTimezoneEditedListener);
203
- }
204
- }
205
159
  }
206
160
 
207
161
  module.exports = CollectionRouter;
@@ -9,7 +9,6 @@
9
9
 
10
10
  const debug = require('@tryghost/debug')('routing:parent-router');
11
11
 
12
- const EventEmitter = require('events').EventEmitter;
13
12
  const express = require('../../../shared/express');
14
13
  const _ = require('lodash');
15
14
  const url = require('url');
@@ -46,10 +45,8 @@ function GhostRouter(options) {
46
45
  return innerRouter;
47
46
  }
48
47
 
49
- class ParentRouter extends EventEmitter {
48
+ class ParentRouter {
50
49
  constructor(name) {
51
- super();
52
-
53
50
  this.identifier = security.identifier.uid(10);
54
51
 
55
52
  this.name = name;
@@ -3,17 +3,15 @@ const urlUtils = require('../../../shared/url-utils');
3
3
  const ParentRouter = require('./ParentRouter');
4
4
  const controllers = require('./controllers');
5
5
 
6
- // This emits its own routing events
7
- const events = require('../../../server/lib/common/events');
8
-
9
6
  /**
10
7
  * @description Resource: pages
11
8
  */
12
9
  class StaticPagesRouter extends ParentRouter {
13
- constructor(RESOURCE_CONFIG) {
10
+ constructor(RESOURCE_CONFIG, routerCreated) {
14
11
  super('StaticPagesRouter');
15
12
 
16
13
  this.RESOURCE_CONFIG = RESOURCE_CONFIG.QUERY.page;
14
+ this.routerCreated = routerCreated;
17
15
 
18
16
  // @NOTE: Permalink is always /:slug, not configure able
19
17
  this.permalinks = {
@@ -50,7 +48,7 @@ class StaticPagesRouter extends ParentRouter {
50
48
  // REGISTER: permalink for static pages
51
49
  this.mountRoute(this.permalinks.getValue({withUrlOptions: true}), controllers.entry);
52
50
 
53
- events.emit('router.created', this);
51
+ this.routerCreated(this);
54
52
  }
55
53
 
56
54
  /**
@@ -6,20 +6,18 @@ const controllers = require('./controllers');
6
6
  const middlewares = require('./middlewares');
7
7
  const ParentRouter = require('./ParentRouter');
8
8
 
9
- // This emits its own routing events
10
- const events = require('../../../server/lib/common/events');
11
-
12
9
  /**
13
10
  * @description Template routes allow you to map individual URLs to specific template files within a Ghost theme
14
11
  */
15
12
  class StaticRoutesRouter extends ParentRouter {
16
- constructor(mainRoute, object) {
13
+ constructor(mainRoute, object, routerCreated) {
17
14
  super('StaticRoutesRouter');
18
15
 
19
16
  this.route = {value: mainRoute};
20
17
  this.templates = object.templates || [];
21
18
  this.data = object.data || {query: {}, router: {}};
22
19
  this.routerName = mainRoute === '/' ? 'index' : mainRoute.replace(/\//g, '');
20
+ this.routerCreated = routerCreated;
23
21
 
24
22
  debug(this.route.value, this.templates);
25
23
 
@@ -64,7 +62,7 @@ class StaticRoutesRouter extends ParentRouter {
64
62
  this.router().param('page', middlewares.pageParam);
65
63
  this.mountRoute(urlUtils.urlJoin(this.route.value, 'page', ':page(\\d+)'), controllers[this.controller]);
66
64
 
67
- events.emit('router.created', this);
65
+ this.routerCreated(this);
68
66
  }
69
67
 
70
68
  /**
@@ -100,7 +98,7 @@ class StaticRoutesRouter extends ParentRouter {
100
98
  // REGISTER: static route
101
99
  this.mountRoute(this.route.value, controllers.static);
102
100
 
103
- events.emit('router.created', this);
101
+ this.routerCreated(this);
104
102
  }
105
103
 
106
104
  /**
@@ -6,15 +6,12 @@ const urlUtils = require('../../../shared/url-utils');
6
6
  const controllers = require('./controllers');
7
7
  const middlewares = require('./middlewares');
8
8
 
9
- // This emits its own routing events
10
- const events = require('../../../server/lib/common/events');
11
-
12
9
  /**
13
10
  * @description Taxonomies are groupings of posts based on a common relation.
14
11
  * Taxonomies do not change the url of a resource.
15
12
  */
16
13
  class TaxonomyRouter extends ParentRouter {
17
- constructor(key, permalinks, RESOURCE_CONFIG) {
14
+ constructor(key, permalinks, RESOURCE_CONFIG, routerCreated) {
18
15
  super('Taxonomy');
19
16
 
20
17
  this.taxonomyKey = key;
@@ -28,6 +25,8 @@ class TaxonomyRouter extends ParentRouter {
28
25
  return this.permalinks.value;
29
26
  };
30
27
 
28
+ this.routerCreated = routerCreated;
29
+
31
30
  debug(this.permalinks);
32
31
 
33
32
  this._registerRoutes();
@@ -60,7 +59,7 @@ class TaxonomyRouter extends ParentRouter {
60
59
  this.mountRoute(urlUtils.urlJoin(this.permalinks.value, 'edit'), this._redirectEditOption.bind(this));
61
60
  }
62
61
 
63
- events.emit('router.created', this);
62
+ this.routerCreated(this);
64
63
  }
65
64
 
66
65
  /**
@@ -3,7 +3,7 @@ const debug = require('@tryghost/debug')('services:routing:controllers:collectio
3
3
  const tpl = require('@tryghost/tpl');
4
4
  const errors = require('@tryghost/errors');
5
5
  const security = require('@tryghost/security');
6
- const bootstrap = require('../bootstrap');
6
+ const {routerManager} = require('../');
7
7
  const themeEngine = require('../../theme-engine');
8
8
  const helpers = require('../helpers');
9
9
 
@@ -72,7 +72,7 @@ module.exports = function collectionController(req, res, next) {
72
72
  * People should always invert their filters to ensure that the database query loads unique posts per collection.
73
73
  */
74
74
  result.posts = _.filter(result.posts, (post) => {
75
- if (bootstrap.internal.owns(res.routerOptions.identifier, post.id)) {
75
+ if (routerManager.owns(res.routerOptions.identifier, post.id)) {
76
76
  return post;
77
77
  }
78
78
 
@@ -1,6 +1,6 @@
1
1
  const debug = require('@tryghost/debug')('services:routing:controllers:emailpost');
2
2
  const config = require('../../../../shared/config');
3
- const bootstrap = require('../bootstrap');
3
+ const {routerManager} = require('../');
4
4
  const urlUtils = require('../../../../shared/url-utils');
5
5
  const helpers = require('../helpers');
6
6
 
@@ -48,7 +48,7 @@ module.exports = function emailPostController(req, res, next) {
48
48
  }
49
49
 
50
50
  if (post.status === 'published') {
51
- return urlUtils.redirect301(res, bootstrap.internal.getUrlByResourceId(post.id, {withSubdirectory: true}));
51
+ return urlUtils.redirect301(res, routerManager.getUrlByResourceId(post.id, {withSubdirectory: true}));
52
52
  }
53
53
 
54
54
  if (res.locals.apiVersion !== 'v0.1' && res.locals.apiVersion !== 'v2') {
@@ -1,7 +1,7 @@
1
1
  const debug = require('@tryghost/debug')('services:routing:controllers:entry');
2
2
  const url = require('url');
3
3
  const config = require('../../../../shared/config');
4
- const bootstrap = require('../bootstrap');
4
+ const {routerManager} = require('../');
5
5
  const urlUtils = require('../../../../shared/url-utils');
6
6
  const helpers = require('../helpers');
7
7
 
@@ -60,7 +60,7 @@ module.exports = function entryController(req, res, next) {
60
60
  *
61
61
  * That's why we have to check against the router type.
62
62
  */
63
- if (bootstrap.internal.getResourceById(entry.id).config.type !== res.routerOptions.resourceType) {
63
+ if (routerManager.getResourceById(entry.id).config.type !== res.routerOptions.resourceType) {
64
64
  debug('not my resource type');
65
65
  return next();
66
66
  }
@@ -1,6 +1,6 @@
1
1
  const debug = require('@tryghost/debug')('services:routing:controllers:preview');
2
2
  const config = require('../../../../shared/config');
3
- const bootstrap = require('../bootstrap');
3
+ const {routerManager} = require('../');
4
4
  const urlUtils = require('../../../../shared/url-utils');
5
5
  const helpers = require('../helpers');
6
6
 
@@ -50,7 +50,7 @@ module.exports = function previewController(req, res, next) {
50
50
  }
51
51
 
52
52
  if (post.status === 'published') {
53
- return urlUtils.redirect301(res, bootstrap.internal.getUrlByResourceId(post.id, {withSubdirectory: true}));
53
+ return urlUtils.redirect301(res, routerManager.getUrlByResourceId(post.id, {withSubdirectory: true}));
54
54
  }
55
55
 
56
56
  if (res.locals.apiVersion !== 'v0.1' && res.locals.apiVersion !== 'v2') {
@@ -1,21 +1,15 @@
1
+ const registry = require('./registry');
2
+ const RouterManager = require('./router-manager');
3
+ const routerManager = new RouterManager({registry});
4
+
1
5
  module.exports = {
2
- get bootstrap() {
3
- return require('./bootstrap');
4
- },
6
+ routerManager: routerManager,
5
7
 
6
8
  get registry() {
7
- return require('./registry');
9
+ return registry;
8
10
  },
9
11
 
10
12
  get helpers() {
11
13
  return require('./helpers');
12
- },
13
-
14
- get CollectionRouter() {
15
- return require('./CollectionRouter');
16
- },
17
-
18
- get TaxonomyRouter() {
19
- return require('./TaxonomyRouter');
20
14
  }
21
15
  };
@@ -41,6 +41,19 @@ module.exports = {
41
41
  return routers[name];
42
42
  },
43
43
 
44
+ /**
45
+ * Gets a router by it's internal router name
46
+ * @param {String} name internal router name
47
+ * @returns {Express-Router}
48
+ */
49
+ getRouterByName(name) {
50
+ for (let routerKey in routers) {
51
+ if (routers[routerKey].name === name) {
52
+ return routers[routerKey];
53
+ }
54
+ }
55
+ },
56
+
44
57
  /**
45
58
  *
46
59
  *
@@ -0,0 +1,185 @@
1
+ const debug = require('@tryghost/debug')('routing');
2
+ const _ = require('lodash');
3
+ const StaticRoutesRouter = require('./StaticRoutesRouter');
4
+ const StaticPagesRouter = require('./StaticPagesRouter');
5
+ const CollectionRouter = require('./CollectionRouter');
6
+ const TaxonomyRouter = require('./TaxonomyRouter');
7
+ const PreviewRouter = require('./PreviewRouter');
8
+ const ParentRouter = require('./ParentRouter');
9
+ const EmailRouter = require('./EmailRouter');
10
+ const UnsubscribeRouter = require('./UnsubscribeRouter');
11
+
12
+ // This emits its own routing events
13
+ const events = require('../../../server/lib/common/events');
14
+
15
+ class RouterManager {
16
+ constructor({registry, defaultApiVersion = 'v4'}) {
17
+ this.registry = registry;
18
+ this.defaultApiVersion = defaultApiVersion;
19
+ this.siteRouter = null;
20
+ this.urlService = null;
21
+ }
22
+
23
+ owns(routerId, id) {
24
+ return this.urlService.owns(routerId, id);
25
+ }
26
+
27
+ getUrlByResourceId(id, options) {
28
+ return this.urlService.getUrlByResourceId(id, options);
29
+ }
30
+
31
+ getResourceById(resourceId) {
32
+ return this.urlService.getResourceById(resourceId);
33
+ }
34
+
35
+ routerCreated(router) {
36
+ // NOTE: this event should be become an "internal frontend even"
37
+ // and should not be consumed by the modules outside the frontend
38
+ events.emit('router.created', this);
39
+
40
+ // CASE: there are router types which do not generate resource urls
41
+ // e.g. static route router, in this case we don't want ot notify the URL service
42
+ if (!router || !router.getPermalinks()) {
43
+ return;
44
+ }
45
+
46
+ this.urlService.onRouterAddedType(router);
47
+ }
48
+
49
+ /**
50
+ * @description The `init` function will return the wrapped parent express router and will start creating all
51
+ * routers if you pass the option "start: true".
52
+ *
53
+ * CASES:
54
+ * - if Ghost starts, it will first init the site app with the wrapper router and then call `start`
55
+ * separately, because it could be that your blog goes into maintenance mode
56
+ * - if you change your route settings, we will re-initialize routing
57
+ *
58
+ * @param {Object} options
59
+ * @param {Boolean} [options.start] - flag controlling if the frontend Routes should be reinitialized
60
+ * @param {String} options.apiVersion - API version frontend Routes should communicate through
61
+ * @param {Object} options.routerSettings - JSON configuration to build frontend Routes
62
+ * @param {Object} options.urlService - service providing resource URL utility functions such as owns, getUrlByResourceId, and getResourceById
63
+ * @returns {ExpressRouter}
64
+ */
65
+ init({start = false, routerSettings, apiVersion, urlService}) {
66
+ this.urlService = urlService;
67
+ debug('routing init', start, apiVersion, routerSettings);
68
+
69
+ this.registry.resetAllRouters();
70
+ this.registry.resetAllRoutes();
71
+
72
+ // NOTE: this event could become an "internal frontend" in the future, it's used has been kept to prevent
73
+ // from tying up this module with sitemaps
74
+ events.emit('routers.reset');
75
+
76
+ this.siteRouter = new ParentRouter('SiteRouter');
77
+ this.registry.setRouter('siteRouter', this.siteRouter);
78
+
79
+ if (start) {
80
+ apiVersion = apiVersion || this.defaultApiVersion;
81
+ this.start(apiVersion, routerSettings);
82
+ }
83
+
84
+ return this.siteRouter.router();
85
+ }
86
+
87
+ /**
88
+ * @description This function will create the routers based on the route settings
89
+ *
90
+ * The routers are created in a specific order. This order defines who can get a resource first or
91
+ * who can dominant other routers.
92
+ *
93
+ * 1. Preview + Unsubscribe Routers: Strongest inbuilt features, which you can never override.
94
+ * 2. Static Routes: Very strong, because you can override any urls and redirect to a static route.
95
+ * 3. Taxonomies: Stronger than collections, because it's an inbuilt feature.
96
+ * 4. Collections
97
+ * 5. Static Pages: Weaker than collections, because we first try to find a post slug and fallback to lookup a static page.
98
+ * 6. Internal Apps: Weakest
99
+ *
100
+ * @param {string} apiVersion
101
+ * @param {object} routerSettings
102
+ */
103
+ start(apiVersion, routerSettings) {
104
+ debug('routing start', apiVersion, routerSettings);
105
+ const RESOURCE_CONFIG = require(`./config/${apiVersion}`);
106
+
107
+ const unsubscribeRouter = new UnsubscribeRouter();
108
+ this.siteRouter.mountRouter(unsubscribeRouter.router());
109
+ this.registry.setRouter('unsubscribeRouter', unsubscribeRouter);
110
+
111
+ const emailRouter = new EmailRouter(RESOURCE_CONFIG);
112
+ this.siteRouter.mountRouter(emailRouter.router());
113
+ this.registry.setRouter('emailRouter', emailRouter);
114
+
115
+ const previewRouter = new PreviewRouter(RESOURCE_CONFIG);
116
+ this.siteRouter.mountRouter(previewRouter.router());
117
+ this.registry.setRouter('previewRouter', previewRouter);
118
+
119
+ _.each(routerSettings.routes, (value, key) => {
120
+ const staticRoutesRouter = new StaticRoutesRouter(key, value, this.routerCreated.bind(this));
121
+ this.siteRouter.mountRouter(staticRoutesRouter.router());
122
+
123
+ this.registry.setRouter(staticRoutesRouter.identifier, staticRoutesRouter);
124
+ });
125
+
126
+ _.each(routerSettings.collections, (value, key) => {
127
+ const collectionRouter = new CollectionRouter(key, value, RESOURCE_CONFIG, this.routerCreated.bind(this));
128
+ this.siteRouter.mountRouter(collectionRouter.router());
129
+ this.registry.setRouter(collectionRouter.identifier, collectionRouter);
130
+ });
131
+
132
+ const staticPagesRouter = new StaticPagesRouter(RESOURCE_CONFIG, this.routerCreated.bind(this));
133
+ this.siteRouter.mountRouter(staticPagesRouter.router());
134
+
135
+ this.registry.setRouter('staticPagesRouter', staticPagesRouter);
136
+
137
+ _.each(routerSettings.taxonomies, (value, key) => {
138
+ const taxonomyRouter = new TaxonomyRouter(key, value, RESOURCE_CONFIG, this.routerCreated.bind(this));
139
+ this.siteRouter.mountRouter(taxonomyRouter.router());
140
+
141
+ this.registry.setRouter(taxonomyRouter.identifier, taxonomyRouter);
142
+ });
143
+
144
+ const appRouter = new ParentRouter('AppsRouter');
145
+ this.siteRouter.mountRouter(appRouter.router());
146
+
147
+ this.registry.setRouter('appRouter', appRouter);
148
+
149
+ debug('Routes:', this.registry.getAllRoutes());
150
+ }
151
+
152
+ /**
153
+ * This is a glue code to keep the implementation of routers away from
154
+ * this sort of logic. Ideally this method should not be ever called
155
+ * and handled completely on the URL Service layer without touching the frontend
156
+ * @param {Object} settingModel instance of the settings model
157
+ * @returns {void}
158
+ */
159
+ handleTimezoneEdit(settingModel) {
160
+ const newTimezone = settingModel.attributes.value;
161
+ const previousTimezone = settingModel._previousAttributes.value;
162
+
163
+ if (newTimezone === previousTimezone) {
164
+ return;
165
+ }
166
+
167
+ /**
168
+ * CASE: timezone changes
169
+ *
170
+ * If your permalink contains a date reference, we have to regenerate the urls.
171
+ *
172
+ * e.g. /:year/:month/:day/:slug/ or /:day/:slug/
173
+ */
174
+
175
+ // NOTE: timezone change only affects the collection router with dated permalinks
176
+ const collectionRouter = this.registry.getRouterByName('CollectionRouter');
177
+ if (collectionRouter.getPermalinks().getValue().match(/:year|:month|:day/)) {
178
+ debug('handleTimezoneEdit: trigger regeneration');
179
+
180
+ this.urlService.onRouterUpdated(collectionRouter);
181
+ }
182
+ }
183
+ }
184
+
185
+ module.exports = RouterManager;
@@ -3,7 +3,7 @@ const Promise = require('bluebird');
3
3
  const cheerio = require('cheerio');
4
4
  const RSS = require('rss');
5
5
  const urlUtils = require('../../../shared/url-utils');
6
- const bootstrap = require('../routing/bootstrap');
6
+ const {routerManager} = require('../routing');
7
7
 
8
8
  const generateTags = function generateTags(data) {
9
9
  if (data.tags) {
@@ -19,7 +19,7 @@ const generateTags = function generateTags(data) {
19
19
  };
20
20
 
21
21
  const generateItem = function generateItem(post, secure) {
22
- const itemUrl = bootstrap.internal.getUrlByResourceId(post.id, {secure, absolute: true});
22
+ const itemUrl = routerManager.getUrlByResourceId(post.id, {secure, absolute: true});
23
23
  const htmlContent = cheerio.load(post.html || '');
24
24
  const item = {
25
25
  title: post.title,