ghost 4.37.0 → 4.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/.c8rc.json +1 -1
  2. package/README.md +26 -18
  3. package/content/themes/casper/LICENSE +1 -1
  4. package/content/themes/casper/README.md +1 -1
  5. package/content/themes/casper/assets/built/global.css +1 -1
  6. package/content/themes/casper/assets/built/global.css.map +1 -1
  7. package/content/themes/casper/assets/built/screen.css +1 -1
  8. package/content/themes/casper/assets/built/screen.css.map +1 -1
  9. package/content/themes/casper/assets/css/global.css +14 -6
  10. package/content/themes/casper/assets/css/screen.css +9 -1
  11. package/content/themes/casper/package.json +2 -2
  12. package/content/themes/casper/partials/post-card.hbs +1 -1
  13. package/content/themes/casper/post.hbs +18 -19
  14. package/content/themes/casper/yarn.lock +186 -217
  15. package/core/built/assets/{chunk.3.4906cf0b01d6d8e33374.js → chunk.3.6e2ed2d00856e12bd81a.js} +19 -19
  16. package/core/built/assets/ghost-dark-498ff8339a89bb68c3f78f59bee4146e.css +1 -0
  17. package/core/built/assets/ghost.min-77b93478f83b0def6ddc5a4f23ce963e.css +1 -0
  18. package/core/built/assets/{ghost.min-c1938f6ee696bf08bd6bf93cac341ea2.js → ghost.min-e6559d901897066aa6a6d4145e3728ed.js} +466 -413
  19. package/core/built/assets/icons/{event-changed-subscription.svg → event-subscriptions.svg} +0 -1
  20. package/core/built/assets/icons/eye.svg +4 -1
  21. package/core/built/assets/icons/member-add.svg +3 -0
  22. package/core/built/assets/icons/member.svg +3 -0
  23. package/core/built/assets/icons/pin.svg +4 -1
  24. package/core/built/assets/{vendor.min-6dc30be68238b5c55df0cdc1f2dc8b8d.js → vendor.min-c39476bced9adb98ee2b292d01c7a8f4.js} +2303 -1372
  25. package/core/frontend/apps/private-blogging/lib/middleware.js +1 -1
  26. package/core/frontend/apps/private-blogging/lib/views/private.hbs +9 -10
  27. package/core/frontend/helpers/get.js +4 -0
  28. package/core/frontend/helpers/match.js +12 -0
  29. package/core/frontend/helpers/prev_post.js +11 -1
  30. package/core/frontend/helpers/tiers.js +59 -0
  31. package/core/frontend/helpers/tpl/content-cta.hbs +1 -1
  32. package/core/frontend/public/ghost.css +205 -143
  33. package/core/frontend/services/routing/router-manager.js +1 -1
  34. package/core/frontend/views/unsubscribe.hbs +28 -33
  35. package/core/frontend/web/middleware/error-handler.js +2 -2
  36. package/core/frontend/web/site.js +10 -0
  37. package/core/server/api/canary/authentication.js +7 -0
  38. package/core/server/api/canary/index.js +4 -0
  39. package/core/server/api/canary/members.js +9 -2
  40. package/core/server/api/canary/products.js +3 -6
  41. package/core/server/api/canary/tiers-public.js +34 -0
  42. package/core/server/api/canary/tiers.js +6 -7
  43. package/core/server/api/canary/utils/serializers/input/pages.js +1 -1
  44. package/core/server/api/canary/utils/serializers/input/posts.js +1 -1
  45. package/core/server/api/canary/utils/serializers/output/email-posts.js +7 -1
  46. package/core/server/api/canary/utils/serializers/output/pages.js +9 -2
  47. package/core/server/api/canary/utils/serializers/output/posts.js +8 -2
  48. package/core/server/api/canary/utils/serializers/output/preview.js +7 -1
  49. package/core/server/api/canary/utils/serializers/output/products.js +3 -1
  50. package/core/server/api/canary/utils/serializers/output/tiers.js +4 -2
  51. package/core/server/api/canary/utils/serializers/output/utils/mapper.js +17 -7
  52. package/core/server/api/shared/serializers/handle.js +2 -2
  53. package/core/server/api/v2/utils/serializers/input/pages.js +1 -1
  54. package/core/server/api/v2/utils/serializers/input/posts.js +1 -1
  55. package/core/server/api/v3/utils/serializers/input/pages.js +1 -1
  56. package/core/server/api/v3/utils/serializers/input/posts.js +1 -1
  57. package/core/server/data/db/connection.js +3 -2
  58. package/core/server/data/importer/import-manager.js +152 -113
  59. package/core/server/data/migrations/versions/3.29/01-remove-duplicate-subscriptions.js +2 -1
  60. package/core/server/data/migrations/versions/3.29/02-remove-duplicate-customers.js +2 -1
  61. package/core/server/data/migrations/versions/3.29/03-remove-orphaned-customers.js +2 -1
  62. package/core/server/data/migrations/versions/3.29/04-remove-orphaned-subscriptions.js +2 -1
  63. package/core/server/data/migrations/versions/3.29/05-add-member-constraints.js +3 -2
  64. package/core/server/data/migrations/versions/3.39/06-add-email-recipient-index.js +4 -3
  65. package/core/server/data/migrations/versions/4.0/14-remove-orphaned-stripe-records.js +2 -1
  66. package/core/server/data/migrations/versions/4.0/26-add-cascade-on-delete.js +2 -1
  67. package/core/server/data/migrations/versions/4.0/29-fix-foreign-key-for-members-stripe-customers-subscriptions.js +2 -1
  68. package/core/server/data/migrations/versions/4.1/02-add-unique-constraint-for-member-stripe-tables.js +2 -1
  69. package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +3 -2
  70. package/core/server/data/migrations/versions/4.33/2022-01-14-11-51-add-default-free-tier.js +3 -0
  71. package/core/server/data/migrations/versions/4.33/2022-01-18-09-07-remove-duplicate-offer-redemptions.js +2 -2
  72. package/core/server/data/migrations/versions/4.35/2022-02-01-11-48-update-email-recipient-filter-column-type.js +2 -1
  73. package/core/server/data/migrations/versions/4.35/2022-02-01-12-03-update-recipient-filter-column-type.js +2 -1
  74. package/core/server/data/migrations/versions/4.37/2022-02-21-09-53-backfill-members-last-seen-at-column.js +3 -2
  75. package/core/server/data/migrations/versions/4.38/2022-03-01-08-46-add-visibility-to-tiers.js +11 -0
  76. package/core/server/data/migrations/versions/4.38/2022-03-03-16-12-add-visibility-to-tiers.js +8 -0
  77. package/core/server/data/migrations/versions/4.38/2022-03-03-16-17-drop-tiers-visible-column.js +7 -0
  78. package/core/server/data/migrations/versions/4.39/2022-03-07-10-57-update-free-products-visibility-column.js +66 -0
  79. package/core/server/data/migrations/versions/4.39/2022-03-07-10-57-update-products-visibility-column.js +36 -0
  80. package/core/server/data/schema/clients/index.js +1 -1
  81. package/core/server/data/schema/clients/mysql.js +4 -4
  82. package/core/server/data/schema/commands.js +42 -50
  83. package/core/server/data/schema/default-settings/default-settings.json +2 -2
  84. package/core/server/data/schema/fixtures/fixtures.json +18 -161
  85. package/core/server/data/schema/schema.js +7 -0
  86. package/core/server/frontend/ghost.min.css +1 -1
  87. package/core/server/lib/image/image-size.js +12 -4
  88. package/core/server/models/base/plugins/generate-slug.js +13 -1
  89. package/core/server/models/base/plugins/raw-knex.js +1 -1
  90. package/core/server/models/post.js +16 -6
  91. package/core/server/models/product.js +2 -1
  92. package/core/server/models/user.js +1 -1
  93. package/core/server/services/auth/api-key/admin.js +15 -6
  94. package/core/server/services/auth/setup.js +34 -13
  95. package/core/server/services/email-analytics/lib/event-processor.js +18 -1
  96. package/core/server/services/mega/mega.js +4 -4
  97. package/core/server/services/mega/template.js +1 -1
  98. package/core/server/services/members/content-gating.js +1 -1
  99. package/core/server/services/members/middleware.js +4 -0
  100. package/core/server/services/members/service.js +13 -1
  101. package/core/server/services/posts/posts-service.js +1 -1
  102. package/core/server/services/url/UrlGenerator.js +1 -1
  103. package/core/server/services/webhooks/webhooks-service.js +2 -0
  104. package/core/server/views/maintenance.html +2 -2
  105. package/core/server/web/admin/views/default-prod.html +4 -4
  106. package/core/server/web/admin/views/default.html +4 -4
  107. package/core/server/web/api/app.js +3 -0
  108. package/core/server/web/api/canary/admin/middleware.js +2 -0
  109. package/core/server/web/api/canary/content/routes.js +1 -0
  110. package/core/server/web/members/app.js +1 -1
  111. package/core/server/web/parent/backend.js +2 -1
  112. package/core/server/web/shared/middleware/uncapitalise.js +3 -2
  113. package/core/shared/config/defaults.json +2 -2
  114. package/core/shared/config/utils.js +5 -1
  115. package/core/shared/labs.js +8 -9
  116. package/core/shared/url-utils.js +4 -1
  117. package/package.json +56 -52
  118. package/yarn.lock +809 -607
  119. package/core/built/assets/ghost-dark-d54723f7267e66fa2595f897076e86c2.css +0 -1
  120. package/core/built/assets/ghost.min-02a5f8954bd85fe28817b8c8b111b8aa.css +0 -1
  121. package/core/built/assets/icons/event-started-subscription.svg +0 -6
  122. package/core/built/assets/icons/locked-email-back.svg +0 -1
  123. package/core/built/assets/icons/locked-email-front.svg +0 -1
  124. package/core/built/assets/icons/locked-email-lock.svg +0 -1
  125. package/core/built/assets/img/ghost-logo-de2acf283f53ba1fd1149928faeaaa74.png +0 -0
@@ -19,6 +19,9 @@ module.exports = createTransactionalMigration(
19
19
  const id = ObjectID().toHexString();
20
20
 
21
21
  logging.info(`Adding tier "${name}"`);
22
+
23
+ // `slugify(id) is used to ensure unique slug here, as existing tiers could be using any name including "Free"
24
+ // slugs are not used anywhere user-facing so the value is acceptable
22
25
  await knex('products')
23
26
  .insert({
24
27
  id: id,
@@ -1,10 +1,10 @@
1
1
  const logging = require('@tryghost/logging');
2
-
3
2
  const {createTransactionalMigration} = require('../../utils');
3
+ const DatabaseInfo = require('@tryghost/database-info');
4
4
 
5
5
  module.exports = createTransactionalMigration(
6
6
  async function up(knex) {
7
- if (knex.client.config.client !== 'mysql') {
7
+ if (!DatabaseInfo.isMySQL(knex)) {
8
8
  logging.warn('Skipping cleanup of duplicate offer redemptions - database is not MySQL');
9
9
  return;
10
10
  }
@@ -1,9 +1,10 @@
1
1
  const logging = require('@tryghost/logging');
2
2
  const {createNonTransactionalMigration} = require('../../utils');
3
+ const DatabaseInfo = require('@tryghost/database-info');
3
4
 
4
5
  module.exports = createNonTransactionalMigration(
5
6
  async function up(knex) {
6
- if (knex.client.config.client === 'sqlite3') {
7
+ if (DatabaseInfo.isSQLite(knex)) {
7
8
  logging.warn('Skipping migration for SQLite3');
8
9
  return;
9
10
  }
@@ -1,9 +1,10 @@
1
1
  const logging = require('@tryghost/logging');
2
2
  const {createNonTransactionalMigration} = require('../../utils');
3
+ const DatabaseInfo = require('@tryghost/database-info');
3
4
 
4
5
  module.exports = createNonTransactionalMigration(
5
6
  async function up(knex) {
6
- if (knex.client.config.client === 'sqlite3') {
7
+ if (DatabaseInfo.isSQLite(knex)) {
7
8
  logging.warn('Skipping migration for SQLite3');
8
9
  return;
9
10
  }
@@ -1,9 +1,10 @@
1
1
  const logging = require('@tryghost/logging');
2
2
  const {createTransactionalMigration} = require('../../utils');
3
+ const DatabaseInfo = require('@tryghost/database-info');
3
4
 
4
5
  module.exports = createTransactionalMigration(
5
6
  async function up(knex) {
6
- if (knex.client.config.client === 'sqlite3') {
7
+ if (DatabaseInfo.isSQLite(knex)) {
7
8
  logging.warn('Skipping migration for SQLite3');
8
9
  return;
9
10
  }
@@ -21,7 +22,7 @@ module.exports = createTransactionalMigration(
21
22
  `);
22
23
  },
23
24
  async function down(knex) {
24
- if (knex.client.config.client === 'sqlite3') {
25
+ if (DatabaseInfo.isSQLite(knex)) {
25
26
  logging.warn('Skipping migration for SQLite3');
26
27
  return;
27
28
  }
@@ -0,0 +1,11 @@
1
+ const {createAddColumnMigration} = require('../../utils');
2
+
3
+ module.exports = createAddColumnMigration('products', 'visible', {
4
+ type: 'boolean',
5
+ nullable: false,
6
+ defaultTo: false
7
+ });
8
+
9
+ module.exports.up = async () => {
10
+ // noop - column will be replaced with `visibility` instead
11
+ };
@@ -0,0 +1,8 @@
1
+ const {createAddColumnMigration} = require('../../utils');
2
+
3
+ module.exports = createAddColumnMigration('products', 'visibility', {
4
+ type: 'string',
5
+ maxlength: 50,
6
+ nullable: false,
7
+ defaultTo: 'none'
8
+ });
@@ -0,0 +1,7 @@
1
+ const {createDropColumnMigration} = require('../../utils');
2
+
3
+ module.exports = createDropColumnMigration('products', 'visible', {});
4
+
5
+ module.exports.down = async () => {
6
+ // noop - column was replaced by `visibility` instead
7
+ };
@@ -0,0 +1,66 @@
1
+ const logging = require('@tryghost/logging');
2
+ const {createTransactionalMigration} = require('../../utils');
3
+
4
+ module.exports = createTransactionalMigration(
5
+ async function up(knex) {
6
+ const portalPlanSetting = await knex('settings').select('value').where('key', 'portal_plans').first();
7
+
8
+ if (!portalPlanSetting) {
9
+ logging.warn('Could not find portal_plans setting - skipping migration');
10
+ return;
11
+ }
12
+
13
+ try {
14
+ const settingData = JSON.parse(portalPlanSetting.value);
15
+
16
+ if (!settingData.includes('free')) {
17
+ logging.warn(`portal_plans does not include "free" - skipping migration`);
18
+ return;
19
+ }
20
+
21
+ logging.info(`Updating free products to visible`);
22
+ await knex('products').update('visibility', 'public').where('type', 'free');
23
+ } catch (err) {
24
+ logging.error(err);
25
+ logging.warn('portal_plans setting is invalid - skipping migration');
26
+ return;
27
+ }
28
+ },
29
+ async function down(knex) {
30
+ const freeTier = await knex('products').select('id').where('type', 'free').first();
31
+ const portalPlanSetting = await knex('settings').select('value').where('key', 'portal_plans').first();
32
+
33
+ if (!freeTier) {
34
+ logging.info('Free tier is not visible, not updating portal_plans');
35
+ return;
36
+ }
37
+
38
+ if (!portalPlanSetting) {
39
+ logging.warn('Could not find portal_plans setting - skipping migration');
40
+ return;
41
+ }
42
+
43
+ try {
44
+ const existingSettingData = JSON.parse(portalPlanSetting.value);
45
+ let settingData;
46
+
47
+ if (freeTier.visibility === 'public') {
48
+ if (existingSettingData.includes('free')) {
49
+ logging.info('portal_plans setting already contains "free" - skipping update');
50
+ return;
51
+ } else {
52
+ settingData = JSON.stringify(existingSettingData.concat('free'));
53
+ }
54
+ } else {
55
+ settingData = JSON.stringify(existingSettingData.filter(value => value !== 'free'));
56
+ }
57
+
58
+ logging.info(`Updating portal_plans to ${settingData}`);
59
+ await knex('settings').update('value', settingData).where('key', 'portal_plans');
60
+ } catch (err) {
61
+ logging.error(err);
62
+ logging.warn('portal_plans setting is invalid - skipping migration');
63
+ return;
64
+ }
65
+ }
66
+ );
@@ -0,0 +1,36 @@
1
+ const logging = require('@tryghost/logging');
2
+ const {createTransactionalMigration} = require('../../utils');
3
+
4
+ module.exports = createTransactionalMigration(
5
+ async function up(knex) {
6
+ const portalProductSetting = await knex('settings').select('value').where('key', 'portal_products').first();
7
+
8
+ if (!portalProductSetting) {
9
+ logging.warn('Could not find portal_products setting - skipping migration');
10
+ return;
11
+ }
12
+
13
+ try {
14
+ const settingData = JSON.parse(portalProductSetting.value);
15
+
16
+ if (settingData.length === 0) {
17
+ logging.warn(`portal_product is empty, skipping migrations`);
18
+ return;
19
+ }
20
+
21
+ logging.info(`Updating ${settingData.length} products to visible, ${settingData}`);
22
+ await knex('products').update('visibility', 'public').whereIn('id', settingData);
23
+ } catch (err) {
24
+ logging.warn('portal_products setting is invalid - skipping migration');
25
+ return;
26
+ }
27
+ },
28
+ async function down(knex) {
29
+ const visibleTiers = await knex('products').select('id').where('visibility', 'public');
30
+
31
+ const settingData = JSON.stringify(visibleTiers.map(obj => obj.id));
32
+
33
+ logging.info(`Updating portal_products to ${settingData}`);
34
+ await knex('settings').update('value', settingData).where('key', 'portal_products');
35
+ }
36
+ );
@@ -3,5 +3,5 @@ const mysql = require('./mysql');
3
3
 
4
4
  module.exports = {
5
5
  sqlite3: sqlite3,
6
- mysql: mysql
6
+ mysql2: mysql
7
7
  };
@@ -1,8 +1,8 @@
1
1
  const _ = require('lodash');
2
2
  const db = require('../../../data/db');
3
3
 
4
- const doRawAndFlatten = function doRaw(query, transaction, flattenFn) {
5
- return (transaction || db.knex).raw(query).then(function (response) {
4
+ const doRawAndFlatten = function doRaw(query, transaction = db.knex, flattenFn) {
5
+ return transaction.raw(query).then(function (response) {
6
6
  return _.flatten(flattenFn(response));
7
7
  });
8
8
  };
@@ -31,8 +31,8 @@ const getColumns = function getColumns(table, transaction) {
31
31
  // a wrong datatype in schema.js some installations using mysql could have been created using the
32
32
  // data type text instead of mediumtext.
33
33
  // For details see: https://github.com/TryGhost/Ghost/issues/1947
34
- const checkPostTable = function checkPostTable(transaction) {
35
- return (transaction || db.knex).raw('SHOW FIELDS FROM posts where Field ="html" OR Field = "markdown"').then(function (response) {
34
+ const checkPostTable = function checkPostTable(transaction = db.knex) {
35
+ return transaction.raw('SHOW FIELDS FROM posts where Field ="html" OR Field = "markdown"').then(function (response) {
36
36
  return _.flatten(_.map(response[0], function (entry) {
37
37
  if (entry.Type.toLowerCase() !== 'mediumtext') {
38
38
  return (transaction || db.knex).raw('ALTER TABLE posts MODIFY ' + entry.Field + ' MEDIUMTEXT');
@@ -4,6 +4,7 @@ const logging = require('@tryghost/logging');
4
4
  const errors = require('@tryghost/errors');
5
5
  const tpl = require('@tryghost/tpl');
6
6
  const db = require('../db');
7
+ const DatabaseInfo = require('@tryghost/database-info');
7
8
  const schema = require('./schema');
8
9
  const clients = require('./clients');
9
10
 
@@ -58,14 +59,14 @@ function addTableColumn(tableName, table, columnName, columnSpec = schema[tableN
58
59
  }
59
60
  }
60
61
 
61
- function addColumn(tableName, column, transaction, columnSpec) {
62
- return (transaction || db.knex).schema.table(tableName, function (table) {
62
+ function addColumn(tableName, column, transaction = db.knex, columnSpec) {
63
+ return transaction.schema.table(tableName, function (table) {
63
64
  addTableColumn(tableName, table, column, columnSpec);
64
65
  });
65
66
  }
66
67
 
67
- function dropColumn(tableName, column, transaction) {
68
- return (transaction || db.knex).schema.table(tableName, function (table) {
68
+ function dropColumn(tableName, column, transaction = db.knex) {
69
+ return transaction.schema.table(tableName, function (table) {
69
70
  table.dropColumn(column);
70
71
  });
71
72
  }
@@ -77,11 +78,11 @@ function dropColumn(tableName, column, transaction) {
77
78
  * @param {string|[string]} columns - column(s) to form unique constraint with
78
79
  * @param {import('knex')} transaction - connection object containing knex reference
79
80
  */
80
- async function addUnique(tableName, columns, transaction) {
81
+ async function addUnique(tableName, columns, transaction = db.knex) {
81
82
  try {
82
83
  logging.info(`Adding unique constraint for: ${columns} in table ${tableName}`);
83
84
 
84
- return await (transaction || db.knex).schema.table(tableName, function (table) {
85
+ return await transaction.schema.table(tableName, function (table) {
85
86
  table.unique(columns);
86
87
  });
87
88
  } catch (err) {
@@ -104,11 +105,11 @@ async function addUnique(tableName, columns, transaction) {
104
105
  * @param {string|[string]} columns - column(s) unique constraint was formed
105
106
  * @param {import('knex')} transaction - connection object containing knex reference
106
107
  */
107
- async function dropUnique(tableName, columns, transaction) {
108
+ async function dropUnique(tableName, columns, transaction = db.knex) {
108
109
  try {
109
110
  logging.info(`Dropping unique constraint for: ${columns} in table: ${tableName}`);
110
111
 
111
- return await (transaction || db.knex).schema.table(tableName, function (table) {
112
+ return await transaction.schema.table(tableName, function (table) {
112
113
  table.dropUnique(columns);
113
114
  });
114
115
  } catch (err) {
@@ -134,17 +135,14 @@ async function dropUnique(tableName, columns, transaction) {
134
135
  * @param {string} configuration.toColumn - column of the table to point the foreign key to
135
136
  * @param {import('knex')} configuration.transaction - connection object containing knex reference
136
137
  */
137
- async function hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, transaction}) {
138
- const knex = (transaction || db.knex);
139
- const client = knex.client.config.client;
140
-
141
- if (client !== 'sqlite3') {
138
+ async function hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, transaction = db.knex}) {
139
+ if (!DatabaseInfo.isSQLite(transaction)) {
142
140
  throw new errors.InternalServerError({
143
141
  message: tpl(messages.hasForeignSQLite3)
144
142
  });
145
143
  }
146
144
 
147
- const foreignKeys = await knex.raw(`PRAGMA foreign_key_list('${fromTable}');`);
145
+ const foreignKeys = await transaction.raw(`PRAGMA foreign_key_list('${fromTable}');`);
148
146
 
149
147
  const hasForeignKey = foreignKeys.some(foreignKey => foreignKey.table === toTable && foreignKey.from === fromColumn && foreignKey.to === toColumn);
150
148
 
@@ -162,9 +160,8 @@ async function hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, trans
162
160
  * @param {Boolean} configuration.cascadeDelete - adds the "on delete cascade" option if true
163
161
  * @param {import('knex')} configuration.transaction - connection object containing knex reference
164
162
  */
165
- async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDelete = false, transaction}) {
166
- const isSQLite = db.knex.client.config.client === 'sqlite3';
167
- if (isSQLite) {
163
+ async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDelete = false, transaction = db.knex}) {
164
+ if (DatabaseInfo.isSQLite(transaction)) {
168
165
  const foreignKeyExists = await hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, transaction});
169
166
  if (foreignKeyExists) {
170
167
  logging.warn(`Skipped adding foreign key from ${fromTable}.${fromColumn} to ${toTable}.${toColumn} - foreign key already exists`);
@@ -176,14 +173,14 @@ async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDele
176
173
 
177
174
  //disable and re-enable foreign key checks on sqlite because of https://github.com/knex/knex/issues/4155
178
175
  let foreignKeysEnabled;
179
- if (isSQLite) {
176
+ if (DatabaseInfo.isSQLite(transaction)) {
180
177
  foreignKeysEnabled = await db.knex.raw('PRAGMA foreign_keys;');
181
178
  if (foreignKeysEnabled[0].foreign_keys) {
182
179
  await db.knex.raw('PRAGMA foreign_keys = OFF;');
183
180
  }
184
181
  }
185
182
 
186
- await (transaction || db.knex).schema.table(fromTable, function (table) {
183
+ await transaction.schema.table(fromTable, function (table) {
187
184
  if (cascadeDelete) {
188
185
  table.foreign(fromColumn).references(`${toTable}.${toColumn}`).onDelete('CASCADE');
189
186
  } else {
@@ -191,13 +188,13 @@ async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDele
191
188
  }
192
189
  });
193
190
 
194
- if (isSQLite) {
191
+ if (DatabaseInfo.isSQLite(transaction)) {
195
192
  if (foreignKeysEnabled[0].foreign_keys) {
196
193
  await db.knex.raw('PRAGMA foreign_keys = ON;');
197
194
  }
198
195
  }
199
196
  } catch (err) {
200
- if (err.code === 'ER_DUP_KEY') {
197
+ if (err.code === 'ER_DUP_KEY' || err.code === 'ER_FK_DUP_KEY' || err.code === 'ER_FK_DUP_NAME') {
201
198
  logging.warn(`Skipped adding foreign key from ${fromTable}.${fromColumn} to ${toTable}.${toColumn} - foreign key already exists`);
202
199
  return;
203
200
  }
@@ -215,9 +212,8 @@ async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDele
215
212
  * @param {string} configuration.toColumn - column of the table to point the foreign key to
216
213
  * @param {import('knex')} configuration.transaction - connection object containing knex reference
217
214
  */
218
- async function dropForeign({fromTable, fromColumn, toTable, toColumn, transaction}) {
219
- const isSQLite = db.knex.client.config.client === 'sqlite3';
220
- if (isSQLite) {
215
+ async function dropForeign({fromTable, fromColumn, toTable, toColumn, transaction = db.knex}) {
216
+ if (DatabaseInfo.isSQLite(transaction)) {
221
217
  const foreignKeyExists = await hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, transaction});
222
218
  if (!foreignKeyExists) {
223
219
  logging.warn(`Skipped dropping foreign key from ${fromTable}.${fromColumn} to ${toTable}.${toColumn} - foreign key does not exist`);
@@ -229,18 +225,18 @@ async function dropForeign({fromTable, fromColumn, toTable, toColumn, transactio
229
225
 
230
226
  //disable and re-enable foreign key checks on sqlite because of https://github.com/knex/knex/issues/4155
231
227
  let foreignKeysEnabled;
232
- if (isSQLite) {
228
+ if (DatabaseInfo.isSQLite(transaction)) {
233
229
  foreignKeysEnabled = await db.knex.raw('PRAGMA foreign_keys;');
234
230
  if (foreignKeysEnabled[0].foreign_keys) {
235
231
  await db.knex.raw('PRAGMA foreign_keys = OFF;');
236
232
  }
237
233
  }
238
234
 
239
- await (transaction || db.knex).schema.table(fromTable, function (table) {
235
+ await transaction.schema.table(fromTable, function (table) {
240
236
  table.dropForeign(fromColumn);
241
237
  });
242
238
 
243
- if (isSQLite) {
239
+ if (DatabaseInfo.isSQLite(transaction)) {
244
240
  if (foreignKeysEnabled[0].foreign_keys) {
245
241
  await db.knex.raw('PRAGMA foreign_keys = ON;');
246
242
  }
@@ -260,17 +256,14 @@ async function dropForeign({fromTable, fromColumn, toTable, toColumn, transactio
260
256
  * @param {string} tableName - name of the table to check primary key constraint on
261
257
  * @param {import('knex')} transaction - connection object containing knex reference
262
258
  */
263
- async function hasPrimaryKeySQLite(tableName, transaction) {
264
- const knex = (transaction || db.knex);
265
- const client = knex.client.config.client;
266
-
267
- if (client !== 'sqlite3') {
259
+ async function hasPrimaryKeySQLite(tableName, transaction = db.knex) {
260
+ if (!DatabaseInfo.isSQLite(transaction)){
268
261
  throw new errors.InternalServerError({
269
262
  message: tpl(messages.hasPrimaryKeySQLiteError)
270
263
  });
271
264
  }
272
265
 
273
- const rawConstraints = await knex.raw(`PRAGMA index_list('${tableName}');`);
266
+ const rawConstraints = await transaction.raw(`PRAGMA index_list('${tableName}');`);
274
267
  const tablePrimaryKey = rawConstraints.find(c => c.origin === 'pk');
275
268
 
276
269
  return tablePrimaryKey;
@@ -283,9 +276,8 @@ async function hasPrimaryKeySQLite(tableName, transaction) {
283
276
  * @param {string|[string]} columns - column(s) to form primary key constraint with
284
277
  * @param {import('knex')} transaction - connection object containing knex reference
285
278
  */
286
- async function addPrimaryKey(tableName, columns, transaction) {
287
- const isSQLite = db.knex.client.config.client === 'sqlite3';
288
- if (isSQLite) {
279
+ async function addPrimaryKey(tableName, columns, transaction = db.knex) {
280
+ if (DatabaseInfo.isSQLite(transaction)) {
289
281
  const primaryKeyExists = await hasPrimaryKeySQLite(tableName, transaction);
290
282
  if (primaryKeyExists) {
291
283
  logging.warn(`Primary key constraint for: ${columns} already exists for table: ${tableName}`);
@@ -294,7 +286,7 @@ async function addPrimaryKey(tableName, columns, transaction) {
294
286
  }
295
287
  try {
296
288
  logging.info(`Adding primary key constraint for: ${columns} in table ${tableName}`);
297
- return await (transaction || db.knex).schema.table(tableName, function (table) {
289
+ return await transaction.schema.table(tableName, function (table) {
298
290
  table.primary(columns);
299
291
  });
300
292
  } catch (err) {
@@ -316,8 +308,8 @@ async function addPrimaryKey(tableName, columns, transaction) {
316
308
  * @param {import('knex').Transaction} transaction - connection to the DB
317
309
  * @param {Object} [tableSpec] - table schema to generate table with
318
310
  */
319
- function createTable(table, transaction, tableSpec = schema[table]) {
320
- return (transaction || db.knex).schema.createTable(table, function (t) {
311
+ function createTable(table, transaction = db.knex, tableSpec = schema[table]) {
312
+ return transaction.schema.createTable(table, function (t) {
321
313
  Object.keys(tableSpec)
322
314
  .filter(column => !(column.startsWith('@@')))
323
315
  .forEach(column => addTableColumn(table, t, column, tableSpec[column]));
@@ -331,12 +323,12 @@ function createTable(table, transaction, tableSpec = schema[table]) {
331
323
  });
332
324
  }
333
325
 
334
- function deleteTable(table, transaction) {
335
- return (transaction || db.knex).schema.dropTableIfExists(table);
326
+ function deleteTable(table, transaction = db.knex) {
327
+ return transaction.schema.dropTableIfExists(table);
336
328
  }
337
329
 
338
- function getTables(transaction) {
339
- const client = (transaction || db.knex).client.config.client;
330
+ function getTables(transaction = db.knex) {
331
+ const client = transaction.client.config.client;
340
332
 
341
333
  if (_.includes(_.keys(clients), client)) {
342
334
  return clients[client].getTables(transaction);
@@ -345,8 +337,8 @@ function getTables(transaction) {
345
337
  return Promise.reject(tpl(messages.noSupportForDatabase, {client: client}));
346
338
  }
347
339
 
348
- function getIndexes(table, transaction) {
349
- const client = (transaction || db.knex).client.config.client;
340
+ function getIndexes(table, transaction = db.knex) {
341
+ const client = transaction.client.config.client;
350
342
 
351
343
  if (_.includes(_.keys(clients), client)) {
352
344
  return clients[client].getIndexes(table, transaction);
@@ -355,8 +347,8 @@ function getIndexes(table, transaction) {
355
347
  return Promise.reject(tpl(messages.noSupportForDatabase, {client: client}));
356
348
  }
357
349
 
358
- function getColumns(table, transaction) {
359
- const client = (transaction || db.knex).client.config.client;
350
+ function getColumns(table, transaction = db.knex) {
351
+ const client = transaction.client.config.client;
360
352
 
361
353
  if (_.includes(_.keys(clients), client)) {
362
354
  return clients[client].getColumns(table);
@@ -365,10 +357,10 @@ function getColumns(table, transaction) {
365
357
  return Promise.reject(tpl(messages.noSupportForDatabase, {client: client}));
366
358
  }
367
359
 
368
- function checkTables(transaction) {
369
- const client = (transaction || db.knex).client.config.client;
360
+ function checkTables(transaction = db.knex) {
361
+ const client = transaction.client.config.client;
370
362
 
371
- if (client === 'mysql') {
363
+ if (DatabaseInfo.isMySQL(transaction)) {
372
364
  return clients[client].checkPostTable();
373
365
  }
374
366
  }
@@ -126,11 +126,11 @@
126
126
  "type": "string"
127
127
  },
128
128
  "navigation": {
129
- "defaultValue": "[{\"label\":\"Home\",\"url\":\"/\"},{\"label\":\"About\",\"url\":\"/about/\"},{\"label\":\"Collection\",\"url\":\"/tag/getting-started/\"},{\"label\":\"Author\",\"url\":\"/author/ghost/\"},{\"label\":\"Portal\",\"url\":\"/portal/\"}]",
129
+ "defaultValue": "[{\"label\":\"Home\",\"url\":\"/\"},{\"label\":\"About\",\"url\":\"/about/\"}]",
130
130
  "type": "array"
131
131
  },
132
132
  "secondary_navigation": {
133
- "defaultValue": "[{\"label\":\"Data & privacy\",\"url\":\"/privacy/\"},{\"label\":\"Contact\",\"url\":\"/contact/\"},{\"label\":\"Contribute →\",\"url\":\"/contribute/\"}]",
133
+ "defaultValue": "[{\"label\":\"Sign up\",\"url\":\"#/portal/\"}]",
134
134
  "type": "array"
135
135
  },
136
136
  "meta_title": {