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
@@ -29,27 +29,25 @@ class LocalFileStore extends StorageBase {
29
29
 
30
30
  /**
31
31
  * Saves a buffer in the targetPath
32
- * - buffer is an instance of Buffer
33
- * - returns a Promise which returns the full URL to retrieve the data
32
+ * @param {Buffer} buffer is an instance of Buffer
33
+ * @param {String} targetPath path to which the buffer should be written
34
+ * @returns {Promise<String>} a URL to retrieve the data
34
35
  */
35
- saveRaw(buffer, targetPath) {
36
+ async saveRaw(buffer, targetPath) {
36
37
  const storagePath = path.join(this.storagePath, targetPath);
37
38
  const targetDir = path.dirname(storagePath);
38
39
 
39
- return fs.mkdirs(targetDir)
40
- .then(() => {
41
- return fs.writeFile(storagePath, buffer);
42
- })
43
- .then(() => {
44
- // For local file system storage can use relative path so add a slash
45
- const fullUrl = (
46
- urlUtils.urlJoin('/', urlUtils.getSubdir(),
47
- urlUtils.STATIC_IMAGE_URL_PREFIX,
48
- targetPath)
49
- ).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
50
-
51
- return fullUrl;
52
- });
40
+ await fs.mkdirs(targetDir);
41
+ await fs.writeFile(storagePath, buffer);
42
+
43
+ // For local file system storage can use relative path so add a slash
44
+ const fullUrl = (
45
+ urlUtils.urlJoin('/', urlUtils.getSubdir(),
46
+ urlUtils.STATIC_IMAGE_URL_PREFIX,
47
+ targetPath)
48
+ ).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
49
+
50
+ return fullUrl;
53
51
  }
54
52
 
55
53
  /**
@@ -57,34 +55,32 @@ class LocalFileStore extends StorageBase {
57
55
  * - image is the express image object
58
56
  * - returns a promise which ultimately returns the full url to the uploaded image
59
57
  *
60
- * @param image
61
- * @param targetDir
62
- * @returns {*}
58
+ * @param {StorageBase.Image} image
59
+ * @param {String} targetDir
60
+ * @returns {Promise<String>}
63
61
  */
64
- save(image, targetDir) {
62
+ async save(image, targetDir) {
65
63
  let targetFilename;
66
64
 
67
65
  // NOTE: the base implementation of `getTargetDir` returns the format this.storagePath/YYYY/MM
68
66
  targetDir = targetDir || this.getTargetDir(this.storagePath);
69
67
 
70
- return this.getUniqueFileName(image, targetDir).then((filename) => {
71
- targetFilename = filename;
72
- return fs.mkdirs(targetDir);
73
- }).then(() => {
74
- return fs.copy(image.path, targetFilename);
75
- }).then(() => {
76
- // The src for the image must be in URI format, not a file system path, which in Windows uses \
77
- // For local file system storage can use relative path so add a slash
78
- const fullUrl = (
79
- urlUtils.urlJoin('/', urlUtils.getSubdir(),
80
- urlUtils.STATIC_IMAGE_URL_PREFIX,
81
- path.relative(this.storagePath, targetFilename))
82
- ).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
83
-
84
- return fullUrl;
85
- }).catch((e) => {
86
- return Promise.reject(e);
87
- });
68
+ const filename = await this.getUniqueFileName(image, targetDir);
69
+
70
+ targetFilename = filename;
71
+ await fs.mkdirs(targetDir);
72
+
73
+ await fs.copy(image.path, targetFilename);
74
+
75
+ // The src for the image must be in URI format, not a file system path, which in Windows uses \
76
+ // For local file system storage can use relative path so add a slash
77
+ const fullUrl = (
78
+ urlUtils.urlJoin('/', urlUtils.getSubdir(),
79
+ urlUtils.STATIC_IMAGE_URL_PREFIX,
80
+ path.relative(this.storagePath, targetFilename))
81
+ ).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
82
+
83
+ return fullUrl;
88
84
  }
89
85
 
90
86
  exists(fileName, targetDir) {
@@ -1,7 +1,17 @@
1
1
  const adapterManager = require('../../services/adapter-manager');
2
2
 
3
- function getStorage() {
4
- return adapterManager.getAdapter('storage');
3
+ /**
4
+ * @param {'images'|'videos'|'audios'} [feature] - name for the "feature" to enable through adapter, e.g.: images or videos storage
5
+ * @returns {Object} adapter instance
6
+ */
7
+ function getStorage(feature) {
8
+ let adapterName = 'storage';
9
+
10
+ if (feature) {
11
+ adapterName += `:${feature}`;
12
+ }
13
+
14
+ return adapterManager.getAdapter(adapterName);
5
15
  }
6
16
 
7
17
  module.exports.getStorage = getStorage;
@@ -7,7 +7,7 @@ module.exports = {
7
7
  statusCode: 201,
8
8
  permissions: false,
9
9
  query(frame) {
10
- const store = storage.getStorage();
10
+ const store = storage.getStorage('images');
11
11
 
12
12
  if (frame.files) {
13
13
  return Promise
@@ -1,5 +1,11 @@
1
+ const tpl = require('@tryghost/tpl');
2
+ const errors = require('@tryghost/errors');
1
3
  const offersService = require('../../services/offers');
2
4
 
5
+ const messages = {
6
+ offerNotFound: 'Offer not found.'
7
+ };
8
+
3
9
  module.exports = {
4
10
  docName: 'offers',
5
11
 
@@ -21,6 +27,12 @@ module.exports = {
21
27
  permissions: true,
22
28
  async query(frame) {
23
29
  const offer = await offersService.api.getOffer(frame.data);
30
+ if (!offer) {
31
+ throw new errors.NotFoundError({
32
+ message: tpl(messages.offerNotFound)
33
+ });
34
+ }
35
+
24
36
  frame.response = {
25
37
  offers: [offer]
26
38
  };
@@ -38,6 +50,13 @@ module.exports = {
38
50
  ...frame.data.offers[0],
39
51
  id: frame.options.id
40
52
  });
53
+
54
+ if (!offer) {
55
+ throw new errors.NotFoundError({
56
+ message: tpl(messages.offerNotFound)
57
+ });
58
+ }
59
+
41
60
  frame.response = {
42
61
  offers: [offer]
43
62
  };
@@ -55,9 +55,8 @@ module.exports = {
55
55
  this.browse(...arguments);
56
56
  },
57
57
 
58
- edit(models, apiConfig, frame) {
59
- const settingsKeyedJSON = _.keyBy(_.invokeMap(models, 'toJSON'), 'key');
60
- this.browse(settingsKeyedJSON, apiConfig, frame);
58
+ edit() {
59
+ this.browse(...arguments);
61
60
  },
62
61
 
63
62
  download(bytes, apiConfig, frame) {
@@ -1,4 +1,4 @@
1
- const urlService = require('../../../../../../../frontend/services/url');
1
+ const urlService = require('../../../../../../services/url');
2
2
  const urlUtils = require('../../../../../../../shared/url-utils');
3
3
  const localUtils = require('../../../index');
4
4
 
@@ -7,7 +7,7 @@ module.exports = {
7
7
  statusCode: 201,
8
8
  permissions: false,
9
9
  query(frame) {
10
- const store = storage.getStorage();
10
+ const store = storage.getStorage('images');
11
11
 
12
12
  if (frame.files) {
13
13
  return Promise
@@ -1,4 +1,4 @@
1
- const urlService = require('../../../../../../../frontend/services/url');
1
+ const urlService = require('../../../../../../services/url');
2
2
  const urlUtils = require('../../../../../../../shared/url-utils');
3
3
  const localUtils = require('../../../index');
4
4
 
@@ -7,7 +7,7 @@ module.exports = {
7
7
  statusCode: 201,
8
8
  permissions: false,
9
9
  query(frame) {
10
- const store = storage.getStorage();
10
+ const store = storage.getStorage('images');
11
11
 
12
12
  if (frame.files) {
13
13
  return Promise
@@ -55,9 +55,8 @@ module.exports = {
55
55
  this.browse(...arguments);
56
56
  },
57
57
 
58
- edit(models, apiConfig, frame) {
59
- const settingsKeyedJSON = _.keyBy(_.invokeMap(models, 'toJSON'), 'key');
60
- this.browse(settingsKeyedJSON, apiConfig, frame);
58
+ edit() {
59
+ this.browse(...arguments);
61
60
  },
62
61
 
63
62
  download(bytes, apiConfig, frame) {
@@ -1,4 +1,4 @@
1
- const urlService = require('../../../../../../../frontend/services/url');
1
+ const urlService = require('../../../../../../services/url');
2
2
  const urlUtils = require('../../../../../../../shared/url-utils');
3
3
  const localUtils = require('../../../index');
4
4
 
@@ -13,7 +13,7 @@ ImageHandler = {
13
13
  directories: ['images', 'content'],
14
14
 
15
15
  loadFile: function (files, baseDir) {
16
- const store = storage.getStorage();
16
+ const store = storage.getStorage('images');
17
17
  const baseDirRegex = baseDir ? new RegExp('^' + baseDir + '/') : new RegExp('');
18
18
 
19
19
  const imageFolderRegexes = _.map(urlUtils.STATIC_IMAGE_URL_PREFIX.split('/'), function (dir) {
@@ -64,7 +64,7 @@ ImageImporter = {
64
64
  return importData;
65
65
  },
66
66
  doImport: function (imageData) {
67
- const store = storage.getStorage();
67
+ const store = storage.getStorage('images');
68
68
 
69
69
  return Promise.map(imageData, function (image) {
70
70
  return store.save(image, image.targetDir).then(function (result) {
@@ -4,27 +4,26 @@ const schema = require('../../schema').tables;
4
4
  const logging = require('@tryghost/logging');
5
5
  const schemaTables = Object.keys(schema);
6
6
 
7
- module.exports.up = function createTables(options) {
7
+ module.exports.up = async (options) => {
8
8
  const connection = options.connection;
9
9
 
10
- return Promise.mapSeries(schemaTables, function createTable(table) {
10
+ await Promise.mapSeries(schemaTables, async (table) => {
11
11
  logging.info('Creating table: ' + table);
12
- return commands.createTable(table, connection);
12
+ await commands.createTable(table, connection);
13
13
  });
14
14
  };
15
15
 
16
16
  /**
17
- *
18
17
  @TODO: This works, but is very dangerous in the current state of the knex-migrator v3.
19
- @TODO: Enable if knex-migrator v3 is stable.
20
- module.exports.down = function dropTables(options) {
18
+ @TODO: Decide if we should enable or delete this
19
+ module.exports.down = async (options) => {
21
20
  var connection = options.connection;
22
21
 
23
22
  // Reference between tables!
24
23
  schemaTables.reverse();
25
- return Promise.mapSeries(schemaTables, function dropTable(table) {
24
+ await Promise.mapSeries(schemaTables, async (table) => {
26
25
  logging.info('Drop table: ' + table);
27
- return commands.deleteTable(table, connection);
26
+ await commands.deleteTable(table, connection);
28
27
  });
29
28
  };
30
29
  */
@@ -7,20 +7,20 @@ module.exports.config = {
7
7
  transaction: true
8
8
  };
9
9
 
10
- module.exports.up = function insertFixtures(options) {
10
+ module.exports.up = async (options) => {
11
11
  const localOptions = _.merge({
12
12
  context: {internal: true},
13
13
  migrating: true
14
14
  }, options);
15
15
 
16
- return Promise.mapSeries(fixtures.models, function (model) {
16
+ await Promise.mapSeries(fixtures.models, async (model) => {
17
17
  logging.info('Model: ' + model.name);
18
18
 
19
- return fixtures.utils.addFixturesForModel(model, localOptions);
20
- }).then(function () {
21
- return Promise.mapSeries(fixtures.relations, function (relation) {
22
- logging.info('Relation: ' + relation.from.model + ' to ' + relation.to.model);
23
- return fixtures.utils.addFixturesForRelation(relation, localOptions);
24
- });
19
+ await fixtures.utils.addFixturesForModel(model, localOptions);
20
+ });
21
+
22
+ await Promise.mapSeries(fixtures.relations, async (relation) => {
23
+ logging.info('Relation: ' + relation.from.model + ' to ' + relation.to.model);
24
+ await fixtures.utils.addFixturesForRelation(relation, localOptions);
25
25
  });
26
26
  };
@@ -0,0 +1,19 @@
1
+ const utils = require('../../utils');
2
+
3
+ const migration = utils.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
+ });
9
+
10
+ // This reverses an "addTable" migration so that we
11
+ // drop the table going forwards and re-add it going back
12
+ const up = migration.down;
13
+ const down = migration.up;
14
+
15
+ migration.up = up;
16
+ migration.down = down;
17
+
18
+ module.exports = migration;
19
+
@@ -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
+