ghost 4.18.0 → 4.20.1

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 (237) 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 +17 -12
  6. package/core/bridge.js +9 -1
  7. package/core/built/assets/{chunk.3.b80d3e1e6b8556aaff3c.js → chunk.3.777d43e2ce954ba8b2f5.js} +25 -25
  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-88d647a008a5b1dd678a89ae1e55c038.js → ghost.min-26e427944e719b616b8dc7fbb3bbd2f9.js} +709 -422
  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/icons/paintbrush.svg +10 -1
  14. package/core/built/assets/icons/post.svg +3 -1
  15. package/core/built/assets/img/footer-marketplace-bg-572b6c6486a7e26316954d599eaa9f30.png +0 -0
  16. package/core/built/assets/img/marketing/offers-1-f2e1b653c4d5bb90eea9d7a2862530f9.jpg +0 -0
  17. package/core/built/assets/img/marketing/offers-2-28a225d34cc39d133748431536961d00.jpg +0 -0
  18. package/core/built/assets/img/marketing/offers-3-2094c91ab21a16c37fbe6ec16c140160.jpg +0 -0
  19. package/core/built/assets/img/themes/Alto-f4db5af43ca9771c7ac1f754de3ddf2f.png +0 -0
  20. package/core/built/assets/img/themes/Bulletin-57d45b992ff0e26e0acdce7ed4cccd67.png +0 -0
  21. package/core/built/assets/img/themes/Casper-c7e784d7188cc5d7f097d9b6c97b0263.jpg +0 -0
  22. package/core/built/assets/img/themes/Dawn-be81aa8c8caae8fcfb5d5fbec823fdcc.png +0 -0
  23. package/core/built/assets/img/themes/Digest-d3467ac22a290e1ad3a543014758286e.png +0 -0
  24. package/core/built/assets/img/themes/Dope-6f8e0bbc199ce4af9a60859e9e6a74ad.png +0 -0
  25. package/core/built/assets/img/themes/Ease-9c279ea6cec3c0f1823f81c9dd24b116.png +0 -0
  26. package/core/built/assets/img/themes/Edge-0258906309e11fd075a1d9880aa09b20.png +0 -0
  27. package/core/built/assets/img/themes/Edition-d8f508e93bc24bdf2716ae6f8b3d44f8.png +0 -0
  28. package/core/built/assets/img/themes/Editorial-a25a4a34c04dedd858bd5e05ef388b1c.jpg +0 -0
  29. package/core/built/assets/img/themes/Journal-accf0031bbae0919900a049061e65a04.png +0 -0
  30. package/core/built/assets/img/themes/London-3f07efcee9e5bfb9a33827064eb77e70.jpg +0 -0
  31. package/core/built/assets/img/themes/Massively-06edf00108429f7fb8e65f190fba34fe.jpg +0 -0
  32. package/core/built/assets/img/themes/Ruby-11a53c62015612f4b3aca8f503121225.png +0 -0
  33. package/core/built/assets/img/themes/Wave-86e8044c2d76cb57a9030e4c24ac9520.png +0 -0
  34. package/core/built/assets/simplemde/{simplemde-232f69d126310434489071a1891e6d8b.js → simplemde-3ffc0ec9e9fecf29b9a499db678c9e65.js} +14 -14
  35. package/core/built/assets/{vendor.min-7dc7cf9c92175ebfb9cea95c120ee8a7.js → vendor.min-af502ac4142871500fc424f6a5a254ec.js} +2206 -1859
  36. package/core/frontend/apps/amp/lib/router.js +1 -1
  37. package/core/frontend/helpers/match.js +17 -23
  38. package/core/frontend/meta/author-url.js +1 -1
  39. package/core/frontend/meta/url.js +1 -1
  40. package/core/{server → frontend}/public/favicon.ico +0 -0
  41. package/core/{server → frontend}/public/ghost.css +0 -0
  42. package/core/{server → frontend}/public/ghost.min.css +0 -0
  43. package/core/{server → frontend}/public/robots.txt +0 -0
  44. package/core/{server → frontend}/public/sitemap.xsl +0 -0
  45. package/core/frontend/services/proxy.js +1 -1
  46. package/core/frontend/services/rendering.js +1 -1
  47. package/core/frontend/services/routing/CollectionRouter.js +3 -49
  48. package/core/frontend/services/routing/ParentRouter.js +1 -4
  49. package/core/frontend/services/routing/StaticPagesRouter.js +3 -5
  50. package/core/frontend/services/routing/StaticRoutesRouter.js +4 -6
  51. package/core/frontend/services/routing/TaxonomyRouter.js +4 -5
  52. package/core/frontend/services/routing/controllers/collection.js +2 -2
  53. package/core/frontend/services/routing/controllers/email-post.js +2 -2
  54. package/core/frontend/services/routing/controllers/entry.js +2 -2
  55. package/core/frontend/services/routing/controllers/preview.js +2 -2
  56. package/core/frontend/services/routing/index.js +6 -12
  57. package/core/frontend/services/routing/registry.js +13 -0
  58. package/core/frontend/services/routing/router-manager.js +185 -0
  59. package/core/frontend/services/rss/generate-feed.js +2 -2
  60. package/core/frontend/services/theme-engine/i18n/i18n.js +267 -28
  61. package/core/frontend/services/theme-engine/i18n/index.js +1 -1
  62. package/core/frontend/services/theme-engine/i18n/theme-i18n.js +73 -0
  63. package/core/frontend/web/index.js +1 -0
  64. package/core/{server/web/site → frontend/web}/middleware/handle-image-sizes.js +4 -4
  65. package/core/{server/web/site → frontend/web}/middleware/index.js +0 -0
  66. package/core/{server/web/site → frontend/web}/middleware/redirect-ghost-to-admin.js +3 -3
  67. package/core/{server/web/site → frontend/web}/middleware/serve-favicon.js +6 -6
  68. package/core/{server/web/site → frontend/web}/middleware/serve-public-file.js +2 -2
  69. package/core/{server/web/site → frontend/web}/middleware/static-theme.js +3 -3
  70. package/core/frontend/web/routes.js +13 -0
  71. package/core/{server/web/site/app.js → frontend/web/site.js} +12 -16
  72. package/core/server/adapters/storage/LocalFileStorage.js +35 -39
  73. package/core/server/adapters/storage/index.js +12 -2
  74. package/core/server/api/canary/custom-theme-settings.js +2 -2
  75. package/core/server/api/canary/images.js +1 -1
  76. package/core/server/api/canary/oembed.js +2 -2
  77. package/core/server/api/canary/offers.js +29 -1
  78. package/core/server/api/canary/posts-public.js +6 -2
  79. package/core/server/api/canary/products.js +6 -2
  80. package/core/server/api/canary/tags-public.js +6 -2
  81. package/core/server/api/canary/users.js +9 -4
  82. package/core/server/api/canary/utils/serializers/output/custom-theme-settings.js +2 -2
  83. package/core/server/api/canary/utils/serializers/output/notifications.js +1 -0
  84. package/core/server/api/canary/utils/serializers/output/settings.js +2 -3
  85. package/core/server/api/canary/utils/serializers/output/utils/url.js +1 -1
  86. package/core/server/api/canary/utils/validators/input/oembed.js +4 -1
  87. package/core/server/api/canary/utils/validators/input/passwordreset.js +8 -3
  88. package/core/server/api/canary/utils/validators/input/settings.js +5 -4
  89. package/core/server/api/canary/utils/validators/input/setup.js +6 -2
  90. package/core/server/api/canary/utils/validators/input/users.js +6 -2
  91. package/core/server/api/canary/utils/validators/input/webhooks.js +8 -3
  92. package/core/server/api/v2/images.js +1 -1
  93. package/core/server/api/v2/utils/serializers/output/authentication.js +9 -4
  94. package/core/server/api/v2/utils/serializers/output/notifications.js +1 -0
  95. package/core/server/api/v2/utils/serializers/output/users.js +5 -3
  96. package/core/server/api/v2/utils/serializers/output/utils/url.js +1 -1
  97. package/core/server/api/v2/utils/validators/input/images.js +11 -6
  98. package/core/server/api/v2/utils/validators/input/invitations.js +14 -6
  99. package/core/server/api/v2/utils/validators/input/invites.js +6 -2
  100. package/core/server/api/v2/utils/validators/input/oembed.js +6 -2
  101. package/core/server/api/v2/utils/validators/input/passwordreset.js +8 -3
  102. package/core/server/api/v2/utils/validators/input/settings.js +10 -4
  103. package/core/server/api/v2/utils/validators/input/setup.js +6 -2
  104. package/core/server/api/v2/utils/validators/input/users.js +5 -2
  105. package/core/server/api/v3/authentication.js +6 -2
  106. package/core/server/api/v3/authors-public.js +6 -2
  107. package/core/server/api/v3/email.js +9 -4
  108. package/core/server/api/v3/images.js +1 -1
  109. package/core/server/api/v3/integrations.js +7 -3
  110. package/core/server/api/v3/invites.js +6 -3
  111. package/core/server/api/v3/labels.js +10 -5
  112. package/core/server/api/v3/memberSigninUrls.js +5 -2
  113. package/core/server/api/v3/oembed.js +2 -2
  114. package/core/server/api/v3/pages-public.js +5 -2
  115. package/core/server/api/v3/pages.js +6 -3
  116. package/core/server/api/v3/posts-public.js +5 -3
  117. package/core/server/api/v3/posts.js +7 -3
  118. package/core/server/api/v3/preview.js +5 -3
  119. package/core/server/api/v3/session.js +7 -3
  120. package/core/server/api/v3/settings.js +8 -3
  121. package/core/server/api/v3/slugs.js +5 -4
  122. package/core/server/api/v3/utils/serializers/output/authentication.js +10 -4
  123. package/core/server/api/v3/utils/serializers/output/notifications.js +1 -0
  124. package/core/server/api/v3/utils/serializers/output/settings.js +2 -3
  125. package/core/server/api/v3/utils/serializers/output/users.js +6 -2
  126. package/core/server/api/v3/utils/serializers/output/utils/url.js +1 -1
  127. package/core/server/api/v3/utils/validators/input/images.js +12 -7
  128. package/core/server/api/v3/utils/validators/input/invitations.js +14 -6
  129. package/core/server/api/v3/utils/validators/input/invites.js +6 -2
  130. package/core/server/api/v3/utils/validators/input/oembed.js +6 -2
  131. package/core/server/api/v3/utils/validators/input/passwordreset.js +8 -3
  132. package/core/server/api/v3/utils/validators/input/settings.js +5 -4
  133. package/core/server/api/v3/utils/validators/input/setup.js +6 -2
  134. package/core/server/api/v3/utils/validators/input/users.js +6 -2
  135. package/core/server/api/v3/utils/validators/input/webhooks.js +8 -3
  136. package/core/server/data/exporter/table-lists.js +2 -1
  137. package/core/server/data/importer/handlers/image.js +1 -1
  138. package/core/server/data/importer/importers/image.js +1 -1
  139. package/core/server/data/migrations/init/1-create-tables.js +7 -8
  140. package/core/server/data/migrations/init/2-create-fixtures.js +8 -8
  141. package/core/server/data/migrations/versions/4.19/01-add-active-column-to-offers.js +7 -0
  142. package/core/server/data/migrations/versions/4.19/02-add-offer-redemptions-table.js +8 -0
  143. package/core/server/data/migrations/versions/4.20/01-remove-offer-redemptions-table.js +19 -0
  144. package/core/server/data/migrations/versions/4.20/02-remove-offers-table.js +30 -0
  145. package/core/server/data/migrations/versions/4.20/03-add-offers-table.js +21 -0
  146. package/core/server/data/migrations/versions/4.20/04-add-offer-redemptions-table.js +9 -0
  147. package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +41 -0
  148. package/core/server/data/schema/fixtures/utils.js +150 -143
  149. package/core/server/data/schema/schema.js +15 -3
  150. package/core/server/frontend/ghost.min.css +1 -0
  151. package/core/server/lib/image/blog-icon.js +10 -10
  152. package/core/server/lib/image/image-size.js +5 -5
  153. package/core/server/lib/image/image-utils.js +4 -4
  154. package/core/server/lib/image/index.js +1 -2
  155. package/core/server/lib/mobiledoc.js +3 -2
  156. package/core/server/models/action.js +7 -4
  157. package/core/server/models/base/plugins/overrides.js +19 -6
  158. package/core/server/models/custom-theme-setting.js +56 -1
  159. package/core/server/models/index.js +4 -45
  160. package/core/server/models/member.js +5 -0
  161. package/core/server/models/offer-redemption.js +10 -0
  162. package/core/server/models/user.js +2 -1
  163. package/core/server/overrides.js +6 -2
  164. package/core/server/run-update-check.js +0 -3
  165. package/core/server/services/adapter-manager/config.js +1 -0
  166. package/core/server/services/adapter-manager/index.js +9 -5
  167. package/core/server/services/adapter-manager/options-resolver.js +18 -0
  168. package/core/server/services/bulk-email/bulk-email-processor.js +6 -2
  169. package/core/server/services/bulk-email/mailgun.js +1 -1
  170. package/core/server/services/custom-theme-settings.js +10 -4
  171. package/core/server/services/invites/index.js +0 -2
  172. package/core/server/services/invites/invites.js +5 -5
  173. package/core/server/services/mail/GhostMailer.js +18 -10
  174. package/core/server/services/mega/mega.js +3 -3
  175. package/core/server/services/mega/post-email-serializer.js +2 -2
  176. package/core/server/services/members/api.js +3 -4
  177. package/core/server/services/members/emails/signin.js +1 -1
  178. package/core/server/services/members/emails/signup.js +1 -1
  179. package/core/server/services/members/emails/subscribe.js +1 -1
  180. package/core/server/services/members/middleware.js +10 -0
  181. package/core/server/services/members/service.js +2 -1
  182. package/core/server/services/notifications/index.js +1 -1
  183. package/core/server/services/notifications/notifications.js +40 -35
  184. package/core/server/services/oembed.js +4 -9
  185. package/core/server/services/offers/service.js +16 -6
  186. package/core/server/services/permissions/public.js +6 -2
  187. package/core/server/services/route-settings/route-settings.js +1 -1
  188. package/core/server/services/settings/index.js +3 -1
  189. package/core/server/services/settings/settings-bread-service.js +42 -20
  190. package/core/server/services/slack.js +1 -1
  191. package/core/server/services/themes/activate.js +2 -2
  192. package/core/server/services/themes/activation-bridge.js +6 -6
  193. package/core/server/services/themes/storage.js +1 -1
  194. package/core/{frontend → server}/services/url/Queue.js +0 -0
  195. package/core/{frontend → server}/services/url/Resource.js +0 -0
  196. package/core/{frontend → server}/services/url/Resources.js +2 -2
  197. package/core/{frontend → server}/services/url/UrlGenerator.js +14 -14
  198. package/core/{frontend → server}/services/url/UrlService.js +12 -15
  199. package/core/{frontend → server}/services/url/Urls.js +1 -1
  200. package/core/{frontend → server}/services/url/configs/canary.js +0 -0
  201. package/core/{frontend → server}/services/url/configs/v2.js +0 -0
  202. package/core/{frontend → server}/services/url/configs/v3.js +0 -0
  203. package/core/{frontend → server}/services/url/configs/v4.js +0 -0
  204. package/core/{frontend → server}/services/url/index.js +0 -0
  205. package/core/server/services/xmlrpc.js +1 -1
  206. package/core/server/update-check.js +3 -3
  207. package/core/server/web/admin/controller.js +11 -0
  208. package/core/server/web/admin/views/default-prod.html +4 -4
  209. package/core/server/web/admin/views/default.html +4 -4
  210. package/core/server/web/api/app.js +8 -9
  211. package/core/server/web/members/app.js +1 -0
  212. package/core/server/web/oauth/app.js +4 -2
  213. package/core/server/web/parent/backend.js +3 -3
  214. package/core/server/web/parent/frontend.js +2 -2
  215. package/core/server/web/shared/middlewares/api/spam-prevention.js +0 -2
  216. package/core/server/web/shared/middlewares/custom-redirects.js +0 -8
  217. package/core/server/web/shared/middlewares/maintenance.js +1 -1
  218. package/core/server/web/well-known.js +10 -10
  219. package/core/shared/config/defaults.json +2 -2
  220. package/core/shared/config/overrides.json +1 -1
  221. package/core/shared/express.js +10 -0
  222. package/core/shared/html-to-plaintext.js +2 -2
  223. package/core/shared/labs.js +14 -6
  224. package/loggingrc.js +10 -0
  225. package/package.json +60 -57
  226. package/yarn.lock +1317 -914
  227. package/core/built/assets/ghost-dark-13627f10941a7dbb2b12e1d41dc51c34.css +0 -1
  228. package/core/built/assets/ghost.min-d9cbfb4eb2db8915fcd2bf2416218616.css +0 -1
  229. package/core/built/assets/img/themes/London-68501c8ab797de7f2851cf9ea0a28e26.jpg +0 -0
  230. package/core/frontend/services/routing/bootstrap.js +0 -114
  231. package/core/server/public/404-ghost.png +0 -0
  232. package/core/server/public/404-ghost@2x.png +0 -0
  233. package/core/server/web/site/index.js +0 -1
  234. package/core/server/web/site/routes.js +0 -9
  235. package/core/shared/i18n/i18n.js +0 -312
  236. package/core/shared/i18n/index.js +0 -6
  237. package/core/shared/i18n/translations/en.json +0 -675
@@ -0,0 +1,30 @@
1
+ const utils = require('../../utils');
2
+
3
+ const migration = utils.addTable('offers', {
4
+ id: {type: 'string', maxlength: 24, nullable: false, primary: true},
5
+ active: {type: 'boolean', nullable: false, defaultTo: true},
6
+ name: {type: 'string', maxlength: 191, nullable: false, unique: true},
7
+ code: {type: 'string', maxlength: 191, nullable: false, unique: true},
8
+ product_id: {type: 'string', maxlength: 24, nullable: false, references: 'products.id'},
9
+ stripe_coupon_id: {type: 'string', maxlength: 255, nullable: false, unique: true},
10
+ interval: {type: 'string', maxlength: 50, nullable: false},
11
+ currency: {type: 'string', maxlength: 50, nullable: true},
12
+ discount_type: {type: 'string', maxlength: 50, nullable: false},
13
+ discount_amount: {type: 'integer', nullable: false},
14
+ duration: {type: 'string', maxlength: 50, nullable: false},
15
+ duration_in_months: {type: 'integer', nullable: true},
16
+ portal_title: {type: 'string', maxlength: 191, nullable: false},
17
+ portal_description: {type: 'string', maxlength: 2000, nullable: true},
18
+ created_at: {type: 'dateTime', nullable: false},
19
+ updated_at: {type: 'dateTime', nullable: true}
20
+ });
21
+
22
+ // This reverses an "addTable" migration so that we
23
+ // drop the table going forwards and re-add it going back
24
+ const up = migration.down;
25
+ const down = migration.up;
26
+
27
+ migration.up = up;
28
+ migration.down = down;
29
+
30
+ module.exports = migration;
@@ -0,0 +1,21 @@
1
+ const utils = require('../../utils');
2
+
3
+ module.exports = utils.addTable('offers', {
4
+ id: {type: 'string', maxlength: 24, nullable: false, primary: true},
5
+ active: {type: 'boolean', nullable: false, defaultTo: true},
6
+ name: {type: 'string', maxlength: 191, nullable: false, unique: true},
7
+ code: {type: 'string', maxlength: 191, nullable: false, unique: true},
8
+ product_id: {type: 'string', maxlength: 24, nullable: false, references: 'products.id'},
9
+ stripe_coupon_id: {type: 'string', maxlength: 255, nullable: true, unique: true},
10
+ interval: {type: 'string', maxlength: 50, nullable: false},
11
+ currency: {type: 'string', maxlength: 50, nullable: true},
12
+ discount_type: {type: 'string', maxlength: 50, nullable: false},
13
+ discount_amount: {type: 'integer', nullable: false},
14
+ duration: {type: 'string', maxlength: 50, nullable: false},
15
+ duration_in_months: {type: 'integer', nullable: true},
16
+ portal_title: {type: 'string', maxlength: 191, nullable: false},
17
+ portal_description: {type: 'string', maxlength: 2000, nullable: true},
18
+ created_at: {type: 'dateTime', nullable: false},
19
+ updated_at: {type: 'dateTime', nullable: true}
20
+ });
21
+
@@ -0,0 +1,9 @@
1
+ const {addTable} = require('../../utils');
2
+
3
+ module.exports = addTable('offer_redemptions', {
4
+ id: {type: 'string', maxlength: 24, nullable: false, primary: true},
5
+ offer_id: {type: 'string', maxlength: 24, nullable: false, references: 'offers.id', cascadeDelete: true},
6
+ member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
7
+ subscription_id: {type: 'string', maxlength: 24, nullable: false, references: 'members_stripe_customers_subscriptions.id', cascadeDelete: true},
8
+ created_at: {type: 'dateTime', nullable: false}
9
+ });
@@ -0,0 +1,41 @@
1
+ const logging = require('@tryghost/logging');
2
+ const {createNonTransactionalMigration} = require('../../utils');
3
+ const {addUnique} = require('../../../schema/commands');
4
+
5
+ module.exports = createNonTransactionalMigration(
6
+ async function up(knex) {
7
+ logging.info('Dropping NOT NULL constraint for: portal_title in table: offers');
8
+
9
+ await knex.schema.table('offers', function (table) {
10
+ table.dropColumn('portal_title');
11
+ });
12
+
13
+ await knex.schema.table('offers', function (table) {
14
+ table.string('portal_title', 191).nullable();
15
+ });
16
+
17
+ if (knex.client.config.client === 'sqlite3') {
18
+ for (const column of ['name', 'code', 'stripe_coupon_id']) {
19
+ await addUnique('offers', column, knex);
20
+ }
21
+ }
22
+ },
23
+ async function down(knex) {
24
+ logging.info('Adding NOT NULL constraint for: portal_title in table: offers');
25
+
26
+ await knex.schema.table('offers', function (table) {
27
+ table.dropColumn('portal_title');
28
+ });
29
+
30
+ await knex.schema.table('offers', function (table) {
31
+ table.string('portal_title', 191).notNullable();
32
+ });
33
+
34
+ if (knex.client.config.client === 'sqlite3') {
35
+ for (const column of ['name', 'code', 'stripe_coupon_id']) {
36
+ await addUnique('offers', column, knex);
37
+ }
38
+ }
39
+ }
40
+ );
41
+
@@ -47,7 +47,7 @@ const matchObj = function matchObj(match, item) {
47
47
  const matchedObj = {};
48
48
 
49
49
  if (_.isArray(match)) {
50
- _.each(match, function (matchProp) {
50
+ _.each(match, (matchProp) => {
51
51
  matchedObj[matchProp] = item.get(matchProp);
52
52
  });
53
53
  } else {
@@ -76,115 +76,9 @@ const fetchRelationData = function fetchRelationData(relation, options) {
76
76
  return Promise.props(props);
77
77
  };
78
78
 
79
- /**
80
- * ### Add Fixtures for Model
81
- * Takes a model fixture, with a name and some entries and processes these
82
- * into a sequence of promises to get each fixture added.
83
- *
84
- * @param {{name, entries}} modelFixture
85
- * @returns {Promise.<*>}
79
+ /*
80
+ * Find methods - use the local fixtures
86
81
  */
87
- const addFixturesForModel = function addFixturesForModel(modelFixture, options = {}) {
88
- // Clone the fixtures as they get changed in this function.
89
- // The initial blog posts will be added a `published_at` property, which
90
- // would change the fixturesHash.
91
- modelFixture = _.cloneDeep(modelFixture);
92
- // The Post model fixtures need a `published_at` date, where at least the seconds
93
- // are different, otherwise `prev_post` and `next_post` helpers won't workd with
94
- // them.
95
- if (modelFixture.name === 'Post') {
96
- _.forEach(modelFixture.entries, function (post, index) {
97
- if (!post.published_at) {
98
- post.published_at = moment().add(index, 'seconds');
99
- }
100
- });
101
- }
102
-
103
- return Promise.mapSeries(modelFixture.entries, function (entry) {
104
- let data = {};
105
-
106
- // CASE: if id is specified, only query by id
107
- if (entry.id) {
108
- data.id = entry.id;
109
- } else if (entry.slug) {
110
- data.slug = entry.slug;
111
- } else {
112
- data = _.cloneDeep(entry);
113
- }
114
-
115
- if (modelFixture.name === 'Post') {
116
- data.status = 'all';
117
- }
118
-
119
- return models[modelFixture.name].findOne(data, options).then(function (found) {
120
- if (!found) {
121
- return models[modelFixture.name].add(entry, options);
122
- }
123
- });
124
- }).then(function (results) {
125
- return {expected: modelFixture.entries.length, done: _.compact(results).length};
126
- });
127
- };
128
-
129
- /**
130
- * ## Add Fixtures for Relation
131
- * Takes a relation fixtures object, with a from, to and some entries and processes these
132
- * into a sequence of promises, to get each fixture added.
133
- *
134
- * @param {{from, to, entries}} relationFixture
135
- * @returns {Promise.<*>}
136
- */
137
- const addFixturesForRelation = function addFixturesForRelation(relationFixture, options) {
138
- const ops = [];
139
- let max = 0;
140
-
141
- return fetchRelationData(relationFixture, options).then(function getRelationOps(data) {
142
- _.each(relationFixture.entries, function processEntries(entry, key) {
143
- const fromItem = data.from.find(matchFunc(relationFixture.from.match, key));
144
-
145
- // CASE: You add new fixtures e.g. a new role in a new release.
146
- // As soon as an **older** migration script wants to add permissions for any resource, it iterates over the
147
- // permissions for each role. But if the role does not exist yet, it won't find the matching db entry and breaks.
148
- if (!fromItem) {
149
- logging.warn('Skip: Target database entry not found for key: ' + key);
150
- return Promise.resolve();
151
- }
152
-
153
- _.each(entry, function processEntryValues(value, entryKey) {
154
- let toItems = data.to.filter(matchFunc(relationFixture.to.match, entryKey, value));
155
- max += toItems.length;
156
-
157
- // Remove any duplicates that already exist in the collection
158
- toItems = _.reject(toItems, function (item) {
159
- return fromItem
160
- .related(relationFixture.from.relation)
161
- .find((model) => {
162
- const objectToMatch = matchObj(relationFixture.to.match, item);
163
- return Object.keys(objectToMatch).every(function (keyToCheck) {
164
- return model.get(keyToCheck) === objectToMatch[keyToCheck];
165
- });
166
- });
167
- });
168
-
169
- if (toItems && toItems.length > 0) {
170
- ops.push(function addRelationItems() {
171
- return baseUtils.attach(
172
- models[relationFixture.from.Model || relationFixture.from.model],
173
- fromItem.id,
174
- relationFixture.from.relation,
175
- toItems,
176
- options
177
- );
178
- });
179
- }
180
- });
181
- });
182
-
183
- return sequence(ops);
184
- }).then(function (result) {
185
- return {expected: max, done: _(result).map('length').sum()};
186
- });
187
- };
188
82
 
189
83
  /**
190
84
  * ### Find Model Fixture
@@ -194,7 +88,7 @@ const addFixturesForRelation = function addFixturesForRelation(relationFixture,
194
88
  * @returns {Object} model fixture
195
89
  */
196
90
  const findModelFixture = function findModelFixture(modelName) {
197
- return _.find(fixtures.models, function (modelFixture) {
91
+ return _.find(fixtures.models, (modelFixture) => {
198
92
  return modelFixture.name === modelName;
199
93
  });
200
94
  };
@@ -232,7 +126,7 @@ const findModelFixtures = function findModelFixtures(modelName, matchExpr) {
232
126
  * @returns {Object} relation fixture
233
127
  */
234
128
  const findRelationFixture = function findRelationFixture(from, to) {
235
- return _.find(fixtures.relations, function (relation) {
129
+ return _.find(fixtures.relations, (relation) => {
236
130
  return relation.from.model === from && relation.to.model === to;
237
131
  });
238
132
  };
@@ -247,8 +141,8 @@ const findPermissionRelationsForObject = function findPermissionRelationsForObje
247
141
  // Make a copy and delete any entries we don't want
248
142
  const foundRelation = _.cloneDeep(findRelationFixture('Role', 'Permission'));
249
143
 
250
- _.each(foundRelation.entries, function (entry, key) {
251
- _.each(entry, function (perm, obj) {
144
+ _.each(foundRelation.entries, (entry, key) => {
145
+ _.each(entry, (perm, obj) => {
252
146
  if (obj !== objName) {
253
147
  delete entry[obj];
254
148
  }
@@ -262,53 +156,166 @@ const findPermissionRelationsForObject = function findPermissionRelationsForObje
262
156
  return foundRelation;
263
157
  };
264
158
 
265
- const removeFixturesForModel = function removeFixturesForModel(modelFixture, options) {
266
- return Promise.mapSeries(modelFixture.entries, function (entry) {
267
- return models[modelFixture.name].findOne(entry.id ? {id: entry.id} : entry, options).then(function (found) {
268
- if (found) {
269
- return models[modelFixture.name].destroy(_.extend(options, {id: found.id}));
159
+ /*
160
+ * Add and Remove Functions, require access to models
161
+ */
162
+
163
+ /**
164
+ * ### Add Fixtures for Model
165
+ * Takes a model fixture, with a name and some entries and processes these
166
+ * into a sequence of promises to get each fixture added.
167
+ *
168
+ * @param {{name, entries}} modelFixture
169
+ * @returns {Promise<any>}
170
+ */
171
+ const addFixturesForModel = async function addFixturesForModel(modelFixture, options = {}) {
172
+ // Clone the fixtures as they get changed in this function.
173
+ // The initial blog posts will be added a `published_at` property, which
174
+ // would change the fixturesHash.
175
+ modelFixture = _.cloneDeep(modelFixture);
176
+ // The Post model fixtures need a `published_at` date, where at least the seconds
177
+ // are different, otherwise `prev_post` and `next_post` helpers won't workd with
178
+ // them.
179
+ if (modelFixture.name === 'Post') {
180
+ _.forEach(modelFixture.entries, (post, index) => {
181
+ if (!post.published_at) {
182
+ post.published_at = moment().add(index, 'seconds');
270
183
  }
271
184
  });
272
- }).then(function (results) {
273
- return {expected: modelFixture.entries.length, done: results.length};
185
+ }
186
+
187
+ const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
188
+ let data = {};
189
+
190
+ // CASE: if id is specified, only query by id
191
+ if (entry.id) {
192
+ data.id = entry.id;
193
+ } else if (entry.slug) {
194
+ data.slug = entry.slug;
195
+ } else {
196
+ data = _.cloneDeep(entry);
197
+ }
198
+
199
+ if (modelFixture.name === 'Post') {
200
+ data.status = 'all';
201
+ }
202
+
203
+ const found = await models[modelFixture.name].findOne(data, options);
204
+ if (!found) {
205
+ return models[modelFixture.name].add(entry, options);
206
+ }
274
207
  });
208
+
209
+ return {expected: modelFixture.entries.length, done: _.compact(results).length};
275
210
  };
276
211
 
277
- const removeFixturesForRelation = function removeFixturesForRelation(relationFixture, options) {
278
- return fetchRelationData(relationFixture, options).then(function getRelationOps(data) {
279
- const ops = [];
280
-
281
- _.each(relationFixture.entries, function processEntries(entry, key) {
282
- const fromItem = data.from.find(matchFunc(relationFixture.from.match, key));
283
-
284
- _.each(entry, function processEntryValues(value, entryKey) {
285
- const toItems = data.to.filter(matchFunc(relationFixture.to.match, entryKey, value));
286
-
287
- if (toItems && toItems.length > 0) {
288
- ops.push(function detachRelation() {
289
- return baseUtils.detach(
290
- models[relationFixture.from.Model || relationFixture.from.model],
291
- fromItem.id,
292
- relationFixture.from.relation,
293
- toItems,
294
- options
295
- );
212
+ /**
213
+ * ## Add Fixtures for Relation
214
+ * Takes a relation fixtures object, with a from, to and some entries and processes these
215
+ * into a sequence of promises, to get each fixture added.
216
+ *
217
+ * @param {{from, to, entries}} relationFixture
218
+ * @returns {Promise<any>}
219
+ */
220
+ const addFixturesForRelation = async function addFixturesForRelation(relationFixture, options) {
221
+ const ops = [];
222
+ let max = 0;
223
+
224
+ const data = await fetchRelationData(relationFixture, options);
225
+
226
+ _.each(relationFixture.entries, (entry, key) => {
227
+ const fromItem = data.from.find(matchFunc(relationFixture.from.match, key));
228
+
229
+ // CASE: You add new fixtures e.g. a new role in a new release.
230
+ // As soon as an **older** migration script wants to add permissions for any resource, it iterates over the
231
+ // permissions for each role. But if the role does not exist yet, it won't find the matching db entry and breaks.
232
+ if (!fromItem) {
233
+ logging.warn('Skip: Target database entry not found for key: ' + key);
234
+ return Promise.resolve();
235
+ }
236
+
237
+ _.each(entry, (value, entryKey) => {
238
+ let toItems = data.to.filter(matchFunc(relationFixture.to.match, entryKey, value));
239
+ max += toItems.length;
240
+
241
+ // Remove any duplicates that already exist in the collection
242
+ toItems = _.reject(toItems, (item) => {
243
+ return fromItem
244
+ .related(relationFixture.from.relation)
245
+ .find((model) => {
246
+ const objectToMatch = matchObj(relationFixture.to.match, item);
247
+ return Object.keys(objectToMatch).every((keyToCheck) => {
248
+ return model.get(keyToCheck) === objectToMatch[keyToCheck];
249
+ });
296
250
  });
297
- }
298
251
  });
252
+
253
+ if (toItems && toItems.length > 0) {
254
+ ops.push(function addRelationItems() {
255
+ return baseUtils.attach(
256
+ models[relationFixture.from.Model || relationFixture.from.model],
257
+ fromItem.id,
258
+ relationFixture.from.relation,
259
+ toItems,
260
+ options
261
+ );
262
+ });
263
+ }
299
264
  });
265
+ });
266
+
267
+ const result = await sequence(ops);
268
+ return {expected: max, done: _(result).map('length').sum()};
269
+ };
300
270
 
301
- return sequence(ops);
271
+ const removeFixturesForModel = async function removeFixturesForModel(modelFixture, options) {
272
+ const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
273
+ const found = models[modelFixture.name].findOne(entry.id ? {id: entry.id} : entry, options);
274
+ if (found) {
275
+ return models[modelFixture.name].destroy(_.extend(options, {id: found.id}));
276
+ }
302
277
  });
278
+
279
+ return {expected: modelFixture.entries.length, done: results.length};
280
+ };
281
+
282
+ const removeFixturesForRelation = async function removeFixturesForRelation(relationFixture, options) {
283
+ const data = await fetchRelationData(relationFixture, options);
284
+ const ops = [];
285
+
286
+ _.each(relationFixture.entries, (entry, key) => {
287
+ const fromItem = data.from.find(matchFunc(relationFixture.from.match, key));
288
+
289
+ _.each(entry, (value, entryKey) => {
290
+ const toItems = data.to.filter(matchFunc(relationFixture.to.match, entryKey, value));
291
+
292
+ if (toItems && toItems.length > 0) {
293
+ ops.push(function detachRelation() {
294
+ return baseUtils.detach(
295
+ models[relationFixture.from.Model || relationFixture.from.model],
296
+ fromItem.id,
297
+ relationFixture.from.relation,
298
+ toItems,
299
+ options
300
+ );
301
+ });
302
+ }
303
+ });
304
+ });
305
+
306
+ return await sequence(ops);
303
307
  };
304
308
 
305
309
  module.exports = {
306
- addFixturesForModel: addFixturesForModel,
307
- addFixturesForRelation: addFixturesForRelation,
310
+ // find
308
311
  findModelFixtureEntry: findModelFixtureEntry,
309
312
  findModelFixtures: findModelFixtures,
310
313
  findRelationFixture: findRelationFixture,
311
314
  findPermissionRelationsForObject: findPermissionRelationsForObject,
315
+
316
+ // add / remove
317
+ addFixturesForModel: addFixturesForModel,
318
+ addFixturesForRelation: addFixturesForRelation,
312
319
  removeFixturesForModel: removeFixturesForModel,
313
320
  removeFixturesForRelation: removeFixturesForRelation
314
321
  };
@@ -386,17 +386,18 @@ module.exports = {
386
386
  },
387
387
  offers: {
388
388
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
389
+ active: {type: 'boolean', nullable: false, defaultTo: true},
389
390
  name: {type: 'string', maxlength: 191, nullable: false, unique: true},
390
391
  code: {type: 'string', maxlength: 191, nullable: false, unique: true},
391
392
  product_id: {type: 'string', maxlength: 24, nullable: false, references: 'products.id'},
392
- stripe_coupon_id: {type: 'string', maxlength: 255, nullable: false, unique: true},
393
+ stripe_coupon_id: {type: 'string', maxlength: 255, nullable: true, unique: true},
393
394
  interval: {type: 'string', maxlength: 50, nullable: false, validations: {isIn: [['month', 'year']]}},
394
395
  currency: {type: 'string', maxlength: 50, nullable: true},
395
396
  discount_type: {type: 'string', maxlength: 50, nullable: false, validations: {isIn: [['percent', 'amount']]}},
396
397
  discount_amount: {type: 'integer', nullable: false},
397
398
  duration: {type: 'string', maxlength: 50, nullable: false},
398
399
  duration_in_months: {type: 'integer', nullable: true},
399
- portal_title: {type: 'string', maxlength: 191, nullable: false},
400
+ portal_title: {type: 'string', maxlength: 191, nullable: true},
400
401
  portal_description: {type: 'string', maxlength: 2000, nullable: true},
401
402
  created_at: {type: 'dateTime', nullable: false},
402
403
  updated_at: {type: 'dateTime', nullable: true}
@@ -528,6 +529,13 @@ module.exports = {
528
529
  plan_amount: {type: 'integer', nullable: false},
529
530
  plan_currency: {type: 'string', maxLength: 3, nullable: false}
530
531
  },
532
+ offer_redemptions: {
533
+ id: {type: 'string', maxlength: 24, nullable: false, primary: true},
534
+ offer_id: {type: 'string', maxlength: 24, nullable: false, references: 'offers.id', cascadeDelete: true},
535
+ member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
536
+ subscription_id: {type: 'string', maxlength: 24, nullable: false, references: 'members_stripe_customers_subscriptions.id', cascadeDelete: true},
537
+ created_at: {type: 'dateTime', nullable: false}
538
+ },
531
539
  members_subscribe_events: {
532
540
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
533
541
  member_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'members.id', cascadeDelete: true},
@@ -671,7 +679,11 @@ module.exports = {
671
679
  nullable: false,
672
680
  validations: {
673
681
  isIn: [[
674
- 'select'
682
+ 'select',
683
+ 'boolean',
684
+ 'color',
685
+ 'text',
686
+ 'image'
675
687
  ]]
676
688
  }
677
689
  },
@@ -0,0 +1 @@
1
+ /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-family:sans-serif}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.darkgrey{color:#343f44}.midgrey{color:#738a94}.lightgrey{color:#e5eff5}.blue{color:#3eb0ef}.red{color:#f05230}.orange{color:#fecd35}.green{color:#a4d037}.darkgrey-hover:hover{color:#343f44}.midgrey-hover:hover{color:#738a94}.lightgrey-hover:hover{color:#e5eff5}.blue-hover:hover{color:#3eb0ef}.red-hover:hover{color:#f05230}.orange-hover:hover{color:#fecd35}.green-hover:hover{color:#a4d037}*,:after,:before{box-sizing:border-box}html{-webkit-tap-highlight-color:rgba(0,0,0,0);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:62.5%;letter-spacing:.2px;line-height:1.65;overflow:hidden}body,html{height:100%;width:100%}body{color:#343f44;font-size:1.4rem;overflow:auto;overflow-x:hidden}.gh-view{-ms-flex-positive:1;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;flex-grow:1}h1,h2{text-rendering:optimizeLegibility;color:#343f44;font-size:2.9rem;line-height:1.15em;margin:0 0 .3em;text-indent:-1px}@media (max-width:500px){h1{font-size:2.4rem}}.gh-input{-webkit-appearance:none;border:1px solid #d6e3eb;border-radius:4px;color:#4b5b62;display:block;font-size:1.6rem;font-weight:300;height:40px;line-height:1em;padding:10px 12px;transition:border-color .15s linear;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;width:100%}.gh-input:focus{border-color:#b4cbda;outline:0}.gh-btn{fill:#829aa8;-webkit-font-smoothing:subpixel-antialiased;border:1px solid #d6e3eb;border-radius:5px;color:#829aa8;display:inline-block;outline:none;text-decoration:none!important;text-shadow:0 1px 0 #fff;transition:all .2s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.gh-btn span{border-radius:4px;display:block;font-size:1.3rem;font-weight:400;height:33px;letter-spacing:.2px;line-height:33px;padding:0 12px;text-align:center}.gh-btn:hover{border-color:#b4cbda}.gh-btn-hover-blue:hover{border-color:#3eb0ef;color:#3eb0ef}.gh-btn-blue{fill:#fff;background:linear-gradient(#3da1d6,#2288bf);border:0;box-shadow:0 1px 0 rgba(0,0,0,.12);color:#fff;padding:1px;text-shadow:0 -1px 0 rgba(0,0,0,.1);transition:none!important}.gh-btn-blue span{background:linear-gradient(#4ab6f0,#2fa5e4 60%,#2fa5e4 90%,#38a9e5);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.gh-btn-blue:active,.gh-btn-blue:focus{background:#1e78a9}.gh-btn-blue:active span,.gh-btn-blue:focus span{background:#29a0e0;box-shadow:none}.gh-btn-block{display:block;width:100%}.gh-input-icon{display:block;position:relative}.gh-input-icon svg{fill:color(var(--midgrey) l(15%));height:14px;left:10px;position:absolute;top:50%;transform:translateY(-7px);width:auto;z-index:2}.gh-input-icon input{padding-left:35px}.gh-app{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:100%;overflow:hidden}.gh-viewport{max-height:100%;overflow:hidden}.gh-main,.gh-viewport{-ms-flex-positive:1;display:-ms-flexbox;display:flex;flex-grow:1}.gh-main{background:#fff;overflow-y:auto;position:relative}.gh-flow{-ms-flex-positive:1;-ms-flex-direction:column;flex-direction:column;flex-grow:1;min-height:100%;overflow-y:auto}.gh-flow,.gh-flow-head{display:-ms-flexbox;display:flex}.gh-flow-head{-ms-flex-negative:0;-ms-flex-pack:justify;flex-shrink:0;justify-content:space-between;padding-bottom:20px;padding-top:4vh}.gh-flow-content-wrap{-ms-flex-positive:1;-ms-flex-negative:0;-ms-flex-pack:center;flex-grow:1;flex-shrink:0;justify-content:center;margin:0 5%;padding-bottom:8vh}.gh-flow-back,.gh-flow-content-wrap{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex}.gh-flow-back{border:1px solid transparent;border-radius:4px;color:#7d878a;font-weight:100;left:0;margin:0 0 0 3%;padding:2px 9px 2px 5px;position:absolute;top:0;transition:all .3s ease}.gh-flow-back svg{height:12px;line-height:14px;margin-right:4px}.gh-flow-back svg path{stroke:#7d878a;stroke-width:1.2px}.gh-flow-back:hover{border:1px solid #dae1e3}.gh-flow-back-plain{-ms-flex-align:center;align-items:center;color:#7d878a;display:-ms-flexbox;display:flex;font-weight:300;left:0;margin:0 0 0 3%;padding:2px 9px 2px 5px;position:absolute;text-decoration:none;top:0;transition:all .3s ease}.gh-flow-back-plain svg{height:12px;line-height:14px;margin-right:4px}.gh-flow-back-plain svg path{stroke:#7d878a;stroke-width:1.2px}.gh-flow-back-plain:hover{color:#15212a}.gh-flow-nav{-ms-flex:1;flex:1;position:relative}.gh-flow-content{color:#738a94;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;font-size:1.9rem;font-weight:100;line-height:1.5em;max-width:700px;text-align:center;width:100%}.gh-flow-content .gh-input-icon input{padding-left:35px}.gh-flow-content-unsubscribe{font-weight:300}@media (max-width:500px){.gh-flow-head-unsubscribe{padding-top:2.8vh}.gh-flow-content{font-size:4vw}.gh-flow-content-unsubscribe{font-size:1.8rem;line-height:1.6em}}.gh-flow-content header{margin:0 auto;max-width:520px}.gh-flow-content h1{font-size:4.2rem;font-weight:100}@media (max-width:600px){.gh-flow-content h1{font-size:7vw}}.gh-flow-content .gh-btn{display:block;margin:20px auto 0;max-width:400px}.gh-flow-content .form-group{margin-bottom:2.5rem}.gh-flow-content input{border:1px solid #dae1e3;font-size:1.6rem;font-weight:100;line-height:1.4em;padding:10px}.gh-flow-em{font-weight:500}.gh-signin{background:#f8fbfd;border:1px solid #dae1e3;border-radius:5px;margin:30px auto;max-width:400px;padding:40px;position:relative;text-align:left;width:100%}.gh-signin .form-group{margin-bottom:1.5rem}.gh-signin .gh-btn{margin:0}.error-content{flex-grow:1;justify-content:center;padding:8vw;user-select:text}.error-content,.error-details{align-items:center;display:flex}.error-details{margin-bottom:4rem}.error-ghost{height:115px;margin:15px}@media (max-width:630px){.error-ghost{display:none}}.error-code{color:#c5d2d9;font-size:10vw;font-weight:600;letter-spacing:-.4vw;line-height:.9em;margin:0}.error-description{border:none;color:#54666d;font-size:2.3rem;font-weight:300;line-height:1.3em;margin:0;padding:0}.error-message{align-items:center;display:flex;flex-direction:column;margin:15px}.error-message a{font-size:1.4rem;line-height:1;margin:8px 0}.error-link{background-color:transparent;color:#5ba4e5;text-decoration:none;transition:background .3s,color .3s}.error-stack{background-color:hsla(0,0%,100%,.3);margin:1rem auto;max-width:800px;padding:2rem}.error-stack-list{list-style-type:none;margin:0;padding:0}.error-stack-list li{display:block}.error-stack-list li:before{color:#bbb;content:"\21AA";display:inline-block;font-size:1.2rem;margin-right:.5rem}.error-stack-function{font-weight:700}
@@ -3,15 +3,15 @@ const Promise = require('bluebird');
3
3
  const _ = require('lodash');
4
4
  const path = require('path');
5
5
  const errors = require('@tryghost/errors');
6
+ const tpl = require('@tryghost/tpl');
6
7
 
7
8
  const messages = {
8
9
  error: 'Could not fetch icon dimensions.'
9
10
  };
10
11
 
11
12
  class BlogIcon {
12
- constructor({config, tpl, urlUtils, settingsCache, storageUtils}) {
13
+ constructor({config, urlUtils, settingsCache, storageUtils}) {
13
14
  this.config = config;
14
- this.tpl = tpl;
15
15
  this.urlUtils = urlUtils;
16
16
  this.settingsCache = settingsCache;
17
17
  this.storageUtils = storageUtils;
@@ -27,10 +27,10 @@ class BlogIcon {
27
27
  getIconDimensions(storagePath) {
28
28
  return new Promise((resolve, reject) => {
29
29
  let dimensions;
30
-
30
+
31
31
  try {
32
32
  dimensions = sizeOf(storagePath);
33
-
33
+
34
34
  if (dimensions.images) {
35
35
  dimensions.width = _.maxBy(dimensions.images, function (w) {
36
36
  return w.width;
@@ -39,14 +39,14 @@ class BlogIcon {
39
39
  return h.height;
40
40
  }).height;
41
41
  }
42
-
42
+
43
43
  return resolve({
44
44
  width: dimensions.width,
45
45
  height: dimensions.height
46
46
  });
47
47
  } catch (err) {
48
48
  return reject(new errors.ValidationError({
49
- message: this.tpl(messages.error, {
49
+ message: tpl(messages.error, {
50
50
  file: storagePath,
51
51
  error: err.message
52
52
  })
@@ -64,7 +64,7 @@ class BlogIcon {
64
64
  */
65
65
  isIcoImageType(icon) {
66
66
  const blogIcon = icon || this.settingsCache.get('icon');
67
-
67
+
68
68
  return blogIcon.match(/.ico$/i) ? true : false;
69
69
  }
70
70
 
@@ -77,7 +77,7 @@ class BlogIcon {
77
77
  */
78
78
  getIconType(icon) {
79
79
  const blogIcon = icon || this.settingsCache.get('icon');
80
-
80
+
81
81
  return this.isIcoImageType(blogIcon) ? 'x-icon' : 'png';
82
82
  }
83
83
 
@@ -90,7 +90,7 @@ class BlogIcon {
90
90
  */
91
91
  getIconUrl(absolut) {
92
92
  const blogIcon = this.settingsCache.get('icon');
93
-
93
+
94
94
  if (absolut) {
95
95
  if (blogIcon) {
96
96
  return this.isIcoImageType(blogIcon) ? this.urlUtils.urlFor({relativeUrl: '/favicon.ico'}, true) : this.urlUtils.urlFor({relativeUrl: '/favicon.png'}, true);
@@ -115,7 +115,7 @@ class BlogIcon {
115
115
  */
116
116
  getIconPath() {
117
117
  const blogIcon = this.settingsCache.get('icon');
118
-
118
+
119
119
  if (blogIcon) {
120
120
  return this.storageUtils.getLocalFileStoragePath(blogIcon);
121
121
  } else {
@@ -6,6 +6,7 @@ const path = require('path');
6
6
  const Promise = require('bluebird');
7
7
  const _ = require('lodash');
8
8
  const errors = require('@tryghost/errors');
9
+ const tpl = require('@tryghost/tpl');
9
10
 
10
11
  const messages = {
11
12
  invalidDimensions: 'Could not fetch image dimensions.'
@@ -17,9 +18,8 @@ const FETCH_ONLY_FORMATS = [
17
18
  ];
18
19
 
19
20
  class ImageSize {
20
- constructor({config, tpl, storage, storageUtils, validator, urlUtils, request}) {
21
+ constructor({config, storage, storageUtils, validator, urlUtils, request}) {
21
22
  this.config = config;
22
- this.tpl = tpl;
23
23
  this.storage = storage;
24
24
  this.storageUtils = storageUtils;
25
25
  this.validator = validator;
@@ -216,7 +216,7 @@ class ImageSize {
216
216
  // get the storage readable filePath
217
217
  filePath = this.storageUtils.getLocalFileStoragePath(imagePath);
218
218
 
219
- return this.storage.getStorage()
219
+ return this.storage.getStorage('images')
220
220
  .read({path: filePath})
221
221
  .then((buf) => {
222
222
  debug('Image fetched (storage):', filePath);
@@ -267,7 +267,7 @@ class ImageSize {
267
267
  }
268
268
 
269
269
  const originalImagePath = path.join(dir, `${imageName}_o${imageNumber || ''}${ext}`);
270
- const originalImageExists = await this.storage.getStorage().exists(originalImagePath);
270
+ const originalImageExists = await this.storage.getStorage('images').exists(originalImagePath);
271
271
 
272
272
  return this.getImageSizeFromStoragePath(originalImageExists ? originalImagePath : imagePath);
273
273
  }
@@ -320,7 +320,7 @@ class ImageSize {
320
320
  });
321
321
  } catch (err) {
322
322
  return reject(new errors.ValidationError({
323
- message: this.tpl(messages.invalidDimensions, {
323
+ message: tpl(messages.invalidDimensions, {
324
324
  file: imagePath,
325
325
  error: err.message
326
326
  })