ghost 5.1.1 → 5.2.2

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 (30) hide show
  1. package/content/themes/casper/assets/built/screen.css +1 -1
  2. package/content/themes/casper/assets/built/screen.css.map +1 -1
  3. package/content/themes/casper/assets/css/screen.css +4 -4
  4. package/content/themes/casper/package.json +1 -1
  5. package/content/themes/casper/partials/post-card.hbs +11 -2
  6. package/core/built/assets/{ghost-dark-2c7356ee2c7b16f27a1f97932bd67e0b.css → ghost-dark-923c90399aa560625a983a6ae9abb38c.css} +1 -1
  7. package/core/built/assets/{ghost.min-95041d583ba9b90f587a92839509ebd6.js → ghost.min-971a0ff706bbf9e24f0b0a5770c8fc41.js} +159 -156
  8. package/core/built/assets/{ghost.min-e8119caef1eb246cf596cd83eb67b97e.css → ghost.min-c74f50609022cebf13ad28810def68b6.css} +1 -1
  9. package/core/built/assets/img/themes/Casper-19b7267aac5acc6abfaaed7e41eddae8.jpg +0 -0
  10. package/core/server/data/db/connection.js +13 -0
  11. package/core/server/data/importer/importers/data/products.js +29 -2
  12. package/core/server/data/importer/importers/data/stripe-products.js +24 -2
  13. package/core/server/models/base/bookshelf.js +2 -0
  14. package/core/server/models/base/plugins/data-manipulation.js +1 -0
  15. package/core/server/models/base/plugins/relations.js +30 -0
  16. package/core/server/models/post.js +7 -4
  17. package/core/server/models/user.js +32 -4
  18. package/core/server/services/bulk-email/bulk-email-processor.js +1 -1
  19. package/core/server/services/mega/email-preview.js +1 -1
  20. package/core/server/services/mega/mega.js +7 -6
  21. package/core/server/web/admin/views/default-prod.html +3 -3
  22. package/core/server/web/admin/views/default.html +3 -3
  23. package/core/server/web/api/canary/admin/app.js +2 -2
  24. package/core/server/web/api/canary/content/app.js +1 -1
  25. package/core/server/web/members/app.js +3 -3
  26. package/core/shared/config/defaults.json +2 -2
  27. package/core/shared/config/utils.js +0 -9
  28. package/package.json +43 -42
  29. package/yarn.lock +381 -503
  30. package/core/built/assets/img/themes/Casper-c7e784d7188cc5d7f097d9b6c97b0263.jpg +0 -0
@@ -1,4 +1,8 @@
1
+ const _ = require('lodash');
1
2
  const knex = require('knex');
3
+ const os = require('os');
4
+
5
+ const logging = require('@tryghost/logging');
2
6
  const config = require('../../../shared/config');
3
7
  let knexInstance;
4
8
 
@@ -30,6 +34,15 @@ function configure(dbConfig) {
30
34
  // Force bthreads to use child_process backend until a worker_thread-compatible version of sqlite3 is published
31
35
  // https://github.com/mapbox/node-sqlite3/issues/1386
32
36
  process.env.BTHREADS_BACKEND = 'child_process';
37
+
38
+ // In the default SQLite test config we set the path to /tmp/ghost-test.db,
39
+ // but this won't work on Windows, so we need to replace the /tmp bit with
40
+ // the Windows temp folder
41
+ const filename = dbConfig.connection.filename;
42
+ if (process.platform === 'win32' && _.isString(filename) && filename.match(/^\/tmp/)) {
43
+ dbConfig.connection.filename = filename.replace(/^\/tmp/, os.tmpdir());
44
+ logging.info(`Ghost DB path: ${dbConfig.connection.filename}`);
45
+ }
33
46
  }
34
47
 
35
48
  if (client === 'mysql2') {
@@ -1,6 +1,7 @@
1
1
  const _ = require('lodash');
2
2
  const BaseImporter = require('./base');
3
3
  const models = require('../../../../models');
4
+ const debug = require('@tryghost/debug')('importer:products');
4
5
 
5
6
  class ProductsImporter extends BaseImporter {
6
7
  constructor(allDataFromFile) {
@@ -8,12 +9,12 @@ class ProductsImporter extends BaseImporter {
8
9
  modelName: 'Product',
9
10
  dataKeyToImport: 'products',
10
11
  requiredFromFile: ['stripe_prices'],
11
- requiredExistingData: ['stripe_prices']
12
+ requiredExistingData: ['stripe_prices', 'products']
12
13
  });
13
14
  }
14
15
 
15
16
  fetchExisting(modelOptions) {
16
- return models.Product.findAll(_.merge({columns: ['products.id as id']}, modelOptions))
17
+ return models.Product.findAll(_.merge({columns: ['products.id as id', 'name', 'slug']}, modelOptions))
17
18
  .then((existingData) => {
18
19
  this.existingData = existingData.toJSON();
19
20
  });
@@ -58,8 +59,34 @@ class ProductsImporter extends BaseImporter {
58
59
  this.dataToImport = this.dataToImport.filter(item => !invalidProducts.includes(item.id));
59
60
  }
60
61
 
62
+ preventDuplicates() {
63
+ debug('preventDuplicates');
64
+ let duplicateProducts = [];
65
+ _.each(this.dataToImport, (objectInFile) => {
66
+ const existingObject = _.find(
67
+ this.requiredExistingData.products,
68
+ {name: objectInFile.name, slug: objectInFile.slug}
69
+ );
70
+ // CASE: tier already exists
71
+ if (existingObject) {
72
+ debug(`skipping existing product ${objectInFile.name}`);
73
+ this.problems.push({
74
+ message: 'Entry was not imported and ignored. Detected duplicated entry.',
75
+ help: this.modelName,
76
+ context: JSON.stringify({
77
+ product: objectInFile
78
+ })
79
+ });
80
+ duplicateProducts.push(objectInFile.id);
81
+ }
82
+ });
83
+ // ignore products that already exist
84
+ this.dataToImport = this.dataToImport.filter(item => !duplicateProducts.includes(item.id));
85
+ }
86
+
61
87
  replaceIdentifiers() {
62
88
  // this has to be in replaceIdentifiers because it's after required* fields are set
89
+ this.preventDuplicates();
63
90
  this.validateStripePrice();
64
91
  return super.replaceIdentifiers();
65
92
  }
@@ -8,6 +8,7 @@ class StripeProductsImporter extends BaseImporter {
8
8
  super(allDataFromFile, {
9
9
  modelName: 'StripeProduct',
10
10
  dataKeyToImport: 'stripe_products',
11
+ requiredFromFile: ['products'],
11
12
  requiredImportedData: ['products'],
12
13
  requiredExistingData: ['products']
13
14
  });
@@ -41,11 +42,32 @@ class StripeProductsImporter extends BaseImporter {
41
42
  objectInFile.product_id = importedObject.id;
42
43
  return;
43
44
  }
44
- const existingObject = _.find(this.requiredExistingData.products, {id: objectInFile.product_id});
45
+
46
+ const existingObjectById = _.find(this.requiredExistingData.products, {id: objectInFile.product_id});
45
47
  // CASE: the product exists in the db already
46
- if (existingObject) {
48
+ if (existingObjectById) {
47
49
  return;
48
50
  }
51
+
52
+ // CASE: we skipped product import because a product with the same name and slug exists in the DB
53
+ debug('lookup product by name and slug');
54
+ const productFromFile = _.find(
55
+ this.requiredFromFile.products,
56
+ {id: objectInFile.product_id}
57
+ );
58
+ if (productFromFile) {
59
+ // look for the existing product with the same name and slug
60
+ const existingObjectByNameAndSlug = _.find(
61
+ this.requiredExistingData.products,
62
+ {name: productFromFile.name, slug: productFromFile.slug}
63
+ );
64
+ if (existingObjectByNameAndSlug) {
65
+ debug(`resolved ${objectInFile.product_id} to ${existingObjectByNameAndSlug.name}`);
66
+ objectInFile.product_id = existingObjectByNameAndSlug.id;
67
+ return;
68
+ }
69
+ }
70
+
49
71
  // CASE: we don't know what product this is for
50
72
  debug(`ignoring stripe product ${objectInFile.stripe_product_id}`);
51
73
  invalidProducts.push(objectInFile.id);
@@ -61,6 +61,8 @@ ghostBookshelf.plugin(require('./plugins/data-manipulation'));
61
61
 
62
62
  ghostBookshelf.plugin(require('./plugins/overrides'));
63
63
 
64
+ ghostBookshelf.plugin(require('./plugins/relations'));
65
+
64
66
  // Manages nested updates (relationships)
65
67
  ghostBookshelf.plugin('bookshelf-relations', {
66
68
  allowedOptions: ['context', 'importing', 'migrating'],
@@ -31,6 +31,7 @@ module.exports = function (Bookshelf) {
31
31
 
32
32
  _.each(attrs, function each(value, key) {
33
33
  if (value !== null
34
+ && Object.prototype.hasOwnProperty.call(schema.tables, self.tableName)
34
35
  && Object.prototype.hasOwnProperty.call(schema.tables[self.tableName], key)
35
36
  && schema.tables[self.tableName][key].type === 'dateTime') {
36
37
  attrs[key] = moment(value).format('YYYY-MM-DD HH:mm:ss');
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @param {import('bookshelf')} Bookshelf
3
+ */
4
+ module.exports = function (Bookshelf) {
5
+ Bookshelf.Model = Bookshelf.Model.extend({
6
+ /**
7
+ * Return a relation, and load it if it hasn't been loaded already (or force a refresh with the forceRefresh option).
8
+ * refs https://github.com/TryGhost/Team/issues/1626
9
+ * @param {string} name Name of the relation to load
10
+ * @param {Object} [options] Options to pass to the fetch when not yet loaded (or when force refreshing)
11
+ * @param {boolean} [options.forceRefresh] If true, the relation will be fetched again even if it has already been loaded.
12
+ * @returns {Promise<import('bookshelf').Model|import('bookshelf').Collection|null>}
13
+ */
14
+ getLazyRelation: async function (name, options = {}) {
15
+ if (this.relations[name] && !options.forceRefresh) {
16
+ // Relation was already loaded
17
+ return this.relations[name];
18
+ }
19
+
20
+ if (!this[name]) {
21
+ return undefined;
22
+ }
23
+ // Not yet loaded, or force refresh
24
+ // Note that we don't use .refresh on the relation on options.forceRefresh
25
+ // Because the relation can also be a collection, which doesn't have a refresh method
26
+ this.relations[name] = this[name]();
27
+ return this.relations[name].fetch(options);
28
+ }
29
+ });
30
+ };
@@ -645,12 +645,12 @@ Post = ghostBookshelf.Model.extend({
645
645
 
646
646
  // ### Business logic for published_at and published_by
647
647
  // If the current status is 'published' and published_at is not set, set it to now
648
- if (newStatus === 'published' && !publishedAt) {
648
+ if ((newStatus === 'published' || newStatus === 'sent') && !publishedAt) {
649
649
  this.set('published_at', new Date());
650
650
  }
651
651
 
652
652
  // If the current status is 'published' and the status has just changed ensure published_by is set correctly
653
- if (newStatus === 'published' && this.hasChanged('status')) {
653
+ if ((newStatus === 'published' || newStatus === 'sent') && this.hasChanged('status')) {
654
654
  // unless published_by is set and we're importing, set published_by to contextUser
655
655
  if (!(this.get('published_by') && options.importing)) {
656
656
  this.set('published_by', String(this.contextUser(options)));
@@ -666,7 +666,7 @@ Post = ghostBookshelf.Model.extend({
666
666
  if (options.newsletter
667
667
  && !this.get('newsletter_id')
668
668
  && this.hasChanged('status')
669
- && (newStatus === 'published' || newStatus === 'scheduled')) {
669
+ && (newStatus === 'published' || newStatus === 'scheduled' || newStatus === 'sent')) {
670
670
  // Map the passed slug to the id + validate the passed newsletter
671
671
  ops.push(async () => {
672
672
  const newsletter = await Newsletter.findOne({slug: options.newsletter}, {transacting: options.transacting, filter: 'status:active'});
@@ -691,7 +691,7 @@ Post = ghostBookshelf.Model.extend({
691
691
  // ensure draft posts have the email_recipient_filter reset unless an email has already been sent
692
692
  if (newStatus === 'draft' && this.hasChanged('status')) {
693
693
  ops.push(function ensureSendEmailWhenPublishedIsUnchanged() {
694
- return self.related('email').fetch({transacting: options.transacting}).then((email) => {
694
+ return self.getLazyRelation('email', {transacting: options.transacting}).then((email) => {
695
695
  if (!email) {
696
696
  self.set('email_recipient_filter', 'all');
697
697
  self.set('newsletter_id', null);
@@ -705,6 +705,9 @@ Post = ghostBookshelf.Model.extend({
705
705
  const hasEmailOnlyFlag = _.get(attrs, 'posts_meta.email_only') || model.related('posts_meta').get('email_only');
706
706
  if (hasEmailOnlyFlag && (newStatus === 'published') && this.hasChanged('status')) {
707
707
  this.set('status', 'sent');
708
+ } else if (!hasEmailOnlyFlag && (newStatus === 'sent') && this.hasChanged('status')) {
709
+ // Prevent setting status to 'sent' for non email only posts
710
+ this.set('status', 'published');
708
711
  }
709
712
 
710
713
  // If a title is set, not the same as the old title, a draft post, and has never been published
@@ -13,11 +13,17 @@ const validatePassword = require('../lib/validate-password');
13
13
  const permissions = require('../services/permissions');
14
14
  const urlUtils = require('../../shared/url-utils');
15
15
  const activeStates = ['active', 'warn-1', 'warn-2', 'warn-3', 'warn-4'];
16
+ const ASSIGNABLE_ROLES = ['Administrator', 'Editor', 'Author', 'Contributor'];
16
17
 
17
18
  const messages = {
18
19
  valueCannotBeBlank: 'Value in [{tableName}.{columnKey}] cannot be blank.',
19
20
  onlyOneRolePerUserSupported: 'Only one role per user is supported at the moment.',
20
21
  methodDoesNotSupportOwnerRole: 'This method does not support assigning the owner role',
22
+ invalidRoleValue: {
23
+ message: 'Role should be an existing role id or a role name',
24
+ context: 'Invalid role assigned to the user',
25
+ help: 'Change the provided role to a role id or use a role name.'
26
+ },
21
27
  userNotFound: 'User not found',
22
28
  ownerNotFound: 'Owner not found',
23
29
  notEnoughPermission: 'You do not have permission to perform this action',
@@ -507,7 +513,7 @@ User = ghostBookshelf.Model.extend({
507
513
  }
508
514
 
509
515
  ops.push(function update() {
510
- return ghostBookshelf.Model.edit.call(self, data, options).then((user) => {
516
+ return ghostBookshelf.Model.edit.call(self, data, options).then(async (user) => {
511
517
  let roleId;
512
518
 
513
519
  if (!data.roles || !data.roles.length) {
@@ -521,7 +527,29 @@ User = ghostBookshelf.Model.extend({
521
527
  if (roles.models[0].id === roleId) {
522
528
  return;
523
529
  }
524
- return ghostBookshelf.model('Role').findOne({id: roleId});
530
+
531
+ if (ASSIGNABLE_ROLES.includes(roleId)) {
532
+ // return if the role is already assigned
533
+ if (roles.models[0].get('name') === roleId) {
534
+ return;
535
+ }
536
+
537
+ return ghostBookshelf.model('Role').findOne({
538
+ name: roleId
539
+ });
540
+ } else if (ObjectId.isValid(roleId)){
541
+ return ghostBookshelf.model('Role').findOne({
542
+ id: roleId
543
+ });
544
+ } else {
545
+ return Promise.reject(
546
+ new errors.ValidationError({
547
+ message: tpl(messages.invalidRoleValue.message),
548
+ context: tpl(messages.invalidRoleValue.context),
549
+ help: tpl(messages.invalidRoleValue.help)
550
+ })
551
+ );
552
+ }
525
553
  }).then((roleToAssign) => {
526
554
  if (roleToAssign && roleToAssign.get('name') === 'Owner') {
527
555
  return Promise.reject(
@@ -529,9 +557,9 @@ User = ghostBookshelf.Model.extend({
529
557
  message: tpl(messages.methodDoesNotSupportOwnerRole)
530
558
  })
531
559
  );
532
- } else {
560
+ } else if (roleToAssign) {
533
561
  // assign all other roles
534
- return user.roles().updatePivot({role_id: roleId});
562
+ return user.roles().updatePivot({role_id: roleToAssign.id});
535
563
  }
536
564
  }).then(() => {
537
565
  options.status = 'all';
@@ -168,7 +168,7 @@ module.exports = {
168
168
 
169
169
  try {
170
170
  // Load newsletter data on email
171
- await emailBatchModel.relations.email.related('newsletter').fetch(Object.assign({}, {require: false}, knexOptions));
171
+ await emailBatchModel.relations.email.getLazyRelation('newsletter', {require: false, ...knexOptions});
172
172
 
173
173
  // send the email
174
174
  const sendResponse = await this.send(emailBatchModel.relations.email.toJSON(), recipientRows, memberSegment);
@@ -10,7 +10,7 @@ class EmailPreview {
10
10
  * @returns {Promise<Object>}
11
11
  */
12
12
  async generateEmailContent(post, {newsletter, memberSegment} = {}) {
13
- let newsletterModel = post.relations.newsletter ?? await post.related('newsletter').fetch();
13
+ let newsletterModel = await post.getLazyRelation('newsletter');
14
14
  if (!newsletterModel) {
15
15
  if (newsletter) {
16
16
  newsletterModel = await models.Newsletter.findOne({slug: newsletter});
@@ -51,7 +51,7 @@ const getReplyToAddress = (fromAddress, replyAddressOption) => {
51
51
  * @param {Object} options
52
52
  */
53
53
  const getEmailData = async (postModel, options) => {
54
- let newsletter = postModel.relations.newsletter ?? await postModel.related('newsletter').fetch();
54
+ let newsletter = await postModel.getLazyRelation('newsletter');
55
55
  if (!newsletter) {
56
56
  // The postModel doesn't have a newsletter in test emails
57
57
  newsletter = await models.Newsletter.getDefaultNewsletter();
@@ -194,9 +194,8 @@ const addEmail = async (postModel, options) => {
194
194
 
195
195
  const knexOptions = _.pick(options, ['transacting', 'forUpdate']);
196
196
  const filterOptions = {...knexOptions, limit: 1};
197
-
198
- // TODO: this is a hack for https://github.com/TryGhost/Team/issues/1626
199
- const newsletter = postModel.relations.newsletter ?? await postModel.related('newsletter').fetch({require: true, ..._.pick(options, ['transacting'])});
197
+ const sharedOptions = _.pick(options, ['transacting']);
198
+ const newsletter = await postModel.getLazyRelation('newsletter', {require: true, ...sharedOptions});
200
199
 
201
200
  if (newsletter.get('status') !== 'active') {
202
201
  // A post might have been scheduled to an archived newsletter.
@@ -360,9 +359,10 @@ async function sendEmailJob({emailModel, options}) {
360
359
  */
361
360
  async function getEmailMemberRows({emailModel, memberSegment, options}) {
362
361
  const knexOptions = _.pick(options, ['transacting', 'forUpdate']);
362
+ const sharedOptions = _.pick(options, ['transacting']);
363
363
  const filterOptions = Object.assign({}, knexOptions);
364
364
 
365
- const newsletter = emailModel.relations.newsletter ?? await emailModel.related('newsletter').fetch(Object.assign({}, {require: true}, _.pick(options, ['transacting'])));
365
+ const newsletter = await emailModel.getLazyRelation('newsletter', {require: true, ...sharedOptions});
366
366
  const recipientFilter = transformEmailRecipientFilter(newsletter, emailModel.get('recipient_filter'), 'recipient_filter');
367
367
  filterOptions.filter = recipientFilter;
368
368
 
@@ -541,7 +541,8 @@ module.exports = {
541
541
  _partitionMembersBySegment: partitionMembersBySegment,
542
542
  _getEmailMemberRows: getEmailMemberRows,
543
543
  _getFromAddress: getFromAddress,
544
- _getReplyToAddress: getReplyToAddress
544
+ _getReplyToAddress: getReplyToAddress,
545
+ _sendEmailJob: sendEmailJob
545
546
  };
546
547
 
547
548
  /**
@@ -8,7 +8,7 @@
8
8
  <title>Ghost Admin</title>
9
9
 
10
10
 
11
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.1%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.2%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -38,7 +38,7 @@
38
38
 
39
39
 
40
40
  <link rel="stylesheet" href="assets/vendor.min-ba66b98f7c24fa40e061c7ffc94f4e23.css">
41
- <link rel="stylesheet" href="assets/ghost.min-e8119caef1eb246cf596cd83eb67b97e.css" title="light">
41
+ <link rel="stylesheet" href="assets/ghost.min-c74f50609022cebf13ad28810def68b6.css" title="light">
42
42
 
43
43
 
44
44
 
@@ -57,7 +57,7 @@
57
57
 
58
58
 
59
59
  <script src="assets/vendor.min-ea369e6487643585f35409d474b06789.js"></script>
60
- <script src="assets/ghost.min-95041d583ba9b90f587a92839509ebd6.js"></script>
60
+ <script src="assets/ghost.min-971a0ff706bbf9e24f0b0a5770c8fc41.js"></script>
61
61
 
62
62
  </body>
63
63
  </html>
@@ -8,7 +8,7 @@
8
8
  <title>Ghost Admin</title>
9
9
 
10
10
 
11
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.1%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.2%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -38,7 +38,7 @@
38
38
 
39
39
 
40
40
  <link rel="stylesheet" href="assets/vendor.min-ba66b98f7c24fa40e061c7ffc94f4e23.css">
41
- <link rel="stylesheet" href="assets/ghost.min-e8119caef1eb246cf596cd83eb67b97e.css" title="light">
41
+ <link rel="stylesheet" href="assets/ghost.min-c74f50609022cebf13ad28810def68b6.css" title="light">
42
42
 
43
43
 
44
44
 
@@ -57,7 +57,7 @@
57
57
 
58
58
 
59
59
  <script src="assets/vendor.min-ea369e6487643585f35409d474b06789.js"></script>
60
- <script src="assets/ghost.min-95041d583ba9b90f587a92839509ebd6.js"></script>
60
+ <script src="assets/ghost.min-971a0ff706bbf9e24f0b0a5770c8fc41.js"></script>
61
61
 
62
62
  </body>
63
63
  </html>
@@ -16,8 +16,8 @@ module.exports = function setupApiApp() {
16
16
  // API middleware
17
17
 
18
18
  // Body parsing
19
- apiApp.use(bodyParser.json({limit: '1mb'}));
20
- apiApp.use(bodyParser.urlencoded({extended: true, limit: '1mb'}));
19
+ apiApp.use(bodyParser.json({limit: '50mb'}));
20
+ apiApp.use(bodyParser.urlencoded({extended: true, limit: '50mb'}));
21
21
 
22
22
  // Query parsing
23
23
  apiApp.use(boolParser());
@@ -15,7 +15,7 @@ module.exports = function setupApiApp() {
15
15
  // API middleware
16
16
 
17
17
  // @NOTE: req.body is undefined if we don't use this parser, this can trouble if components rely on req.body being present
18
- apiApp.use(bodyParser.json({limit: '1mb'}));
18
+ apiApp.use(bodyParser.json({limit: '50mb'}));
19
19
 
20
20
  // Query parsing
21
21
  apiApp.use(boolParser());
@@ -36,12 +36,12 @@ module.exports = function setupMembersApp() {
36
36
 
37
37
  // Manage newsletter subscription via unsubscribe link
38
38
  membersApp.get('/api/member/newsletters', middleware.getMemberNewsletters);
39
- membersApp.put('/api/member/newsletters', bodyParser.json({limit: '1mb'}), middleware.updateMemberNewsletters);
39
+ membersApp.put('/api/member/newsletters', bodyParser.json({limit: '50mb'}), middleware.updateMemberNewsletters);
40
40
 
41
41
  // Get and update member data
42
42
  membersApp.get('/api/member', middleware.getMemberData);
43
- membersApp.put('/api/member', bodyParser.json({limit: '1mb'}), middleware.updateMemberData);
44
- membersApp.post('/api/member/email', bodyParser.json({limit: '1mb'}), (req, res) => membersService.api.middleware.updateEmailAddress(req, res));
43
+ membersApp.put('/api/member', bodyParser.json({limit: '50mb'}), middleware.updateMemberData);
44
+ membersApp.post('/api/member/email', bodyParser.json({limit: '50mb'}), (req, res) => membersService.api.middleware.updateEmailAddress(req, res));
45
45
 
46
46
  // Manage session
47
47
  membersApp.get('/api/session', middleware.getIdentityToken);
@@ -128,8 +128,8 @@
128
128
  "emailAnalytics": true
129
129
  },
130
130
  "portal": {
131
- "url": "https://unpkg.com/@tryghost/portal@~2.1.0/umd/portal.min.js",
132
- "version": "2.1"
131
+ "url": "https://unpkg.com/@tryghost/portal@~2.2.0/umd/portal.min.js",
132
+ "version": "2.2"
133
133
  },
134
134
  "tenor": {
135
135
  "publicReadOnlyApiKey": null,
@@ -1,6 +1,5 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs-extra');
3
- const os = require('os');
4
3
  const _ = require('lodash');
5
4
 
6
5
  /**
@@ -73,14 +72,6 @@ const sanitizeDatabaseProperties = function sanitizeDatabaseProperties(nconf) {
73
72
 
74
73
  if (nconf.get('database:client') === 'sqlite3') {
75
74
  makePathsAbsolute(nconf, nconf.get('database:connection'), 'database:connection');
76
-
77
- // In the default SQLite test config we set the path to /tmp/ghost-test.db,
78
- // but this won't work on Windows, so we need to replace the /tmp bit with
79
- // the Windows temp folder
80
- const filename = nconf.get('database:connection:filename');
81
- if (_.isString(filename) && filename.match(/^\/tmp/)) {
82
- nconf.set('database:connection:filename', filename.replace(/^\/tmp/, os.tmpdir()));
83
- }
84
75
  }
85
76
  };
86
77