ghost 5.19.3 → 5.21.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 (180) hide show
  1. package/components/tryghost-adapter-manager-5.21.0.tgz +0 -0
  2. package/components/{tryghost-api-framework-5.19.3.tgz → tryghost-api-framework-5.21.0.tgz} +0 -0
  3. package/components/tryghost-api-version-compatibility-service-5.21.0.tgz +0 -0
  4. package/components/tryghost-audience-feedback-5.21.0.tgz +0 -0
  5. package/components/tryghost-bootstrap-socket-5.21.0.tgz +0 -0
  6. package/components/tryghost-constants-5.21.0.tgz +0 -0
  7. package/components/tryghost-custom-theme-settings-service-5.21.0.tgz +0 -0
  8. package/components/tryghost-data-generator-5.21.0.tgz +0 -0
  9. package/components/tryghost-domain-events-5.21.0.tgz +0 -0
  10. package/components/tryghost-email-analytics-provider-mailgun-5.21.0.tgz +0 -0
  11. package/components/tryghost-email-analytics-service-5.21.0.tgz +0 -0
  12. package/components/tryghost-email-content-generator-5.21.0.tgz +0 -0
  13. package/components/tryghost-express-dynamic-redirects-5.21.0.tgz +0 -0
  14. package/components/tryghost-extract-api-key-5.21.0.tgz +0 -0
  15. package/components/tryghost-html-to-plaintext-5.21.0.tgz +0 -0
  16. package/components/{tryghost-job-manager-5.19.3.tgz → tryghost-job-manager-5.21.0.tgz} +0 -0
  17. package/components/tryghost-link-redirects-5.21.0.tgz +0 -0
  18. package/components/tryghost-link-replacer-5.21.0.tgz +0 -0
  19. package/components/tryghost-link-tracking-5.21.0.tgz +0 -0
  20. package/components/tryghost-magic-link-5.21.0.tgz +0 -0
  21. package/components/tryghost-mailgun-client-5.21.0.tgz +0 -0
  22. package/components/tryghost-member-analytics-service-5.21.0.tgz +0 -0
  23. package/components/tryghost-member-attribution-5.21.0.tgz +0 -0
  24. package/components/tryghost-member-events-5.21.0.tgz +0 -0
  25. package/components/tryghost-members-analytics-ingress-5.21.0.tgz +0 -0
  26. package/components/tryghost-members-api-5.21.0.tgz +0 -0
  27. package/components/tryghost-members-csv-5.21.0.tgz +0 -0
  28. package/components/tryghost-members-events-service-5.21.0.tgz +0 -0
  29. package/components/tryghost-members-importer-5.21.0.tgz +0 -0
  30. package/components/tryghost-members-offers-5.21.0.tgz +0 -0
  31. package/components/tryghost-members-payments-5.21.0.tgz +0 -0
  32. package/components/{tryghost-members-ssr-5.19.3.tgz → tryghost-members-ssr-5.21.0.tgz} +0 -0
  33. package/components/tryghost-members-stripe-service-5.21.0.tgz +0 -0
  34. package/components/tryghost-minifier-5.21.0.tgz +0 -0
  35. package/components/tryghost-mw-api-version-mismatch-5.21.0.tgz +0 -0
  36. package/components/tryghost-mw-cache-control-5.21.0.tgz +0 -0
  37. package/components/tryghost-mw-error-handler-5.21.0.tgz +0 -0
  38. package/components/tryghost-mw-session-from-token-5.21.0.tgz +0 -0
  39. package/components/tryghost-mw-update-user-last-seen-5.21.0.tgz +0 -0
  40. package/components/tryghost-mw-vhost-5.21.0.tgz +0 -0
  41. package/components/tryghost-oembed-service-5.21.0.tgz +0 -0
  42. package/components/{tryghost-package-json-5.19.3.tgz → tryghost-package-json-5.21.0.tgz} +0 -0
  43. package/components/tryghost-referrers-5.21.0.tgz +0 -0
  44. package/components/tryghost-security-5.21.0.tgz +0 -0
  45. package/components/tryghost-session-service-5.21.0.tgz +0 -0
  46. package/components/tryghost-settings-path-manager-5.21.0.tgz +0 -0
  47. package/components/tryghost-staff-service-5.21.0.tgz +0 -0
  48. package/components/tryghost-stats-service-5.21.0.tgz +0 -0
  49. package/components/tryghost-tiers-5.21.0.tgz +0 -0
  50. package/components/{tryghost-update-check-service-5.19.3.tgz → tryghost-update-check-service-5.21.0.tgz} +0 -0
  51. package/components/tryghost-verification-trigger-5.21.0.tgz +0 -0
  52. package/components/tryghost-version-notifications-data-service-5.21.0.tgz +0 -0
  53. package/core/boot.js +2 -0
  54. package/core/built/admin/assets/{chunk.143.c035c61595ed02eee886.js → chunk.143.9cddfa7bd1a8b9cf3d4b.js} +7 -7
  55. package/core/built/admin/assets/{chunk.178.998dfbcebcec635146b1.js → chunk.178.6de14cfdb28df721b66e.js} +4 -4
  56. package/core/built/admin/assets/{chunk.613.f1d519ad47e7f9024263.js → chunk.613.695f31829550fb00d43c.js} +352 -421
  57. package/core/built/admin/assets/{chunk.613.f1d519ad47e7f9024263.js.LICENSE.txt → chunk.613.695f31829550fb00d43c.js.LICENSE.txt} +0 -0
  58. package/core/built/admin/assets/{ghost-5ce6f5a730c83c91fc258b12c537ea35.js → ghost-192fee3b46a193df1e65c49a67a7d694.js} +2866 -2707
  59. package/core/built/admin/assets/ghost-9873519a8ad69b5b23284f0a9e050bc6.css +1 -0
  60. package/core/built/admin/assets/ghost-dark-190bdce42b125c3d4be930bd7599b442.css +1 -0
  61. package/core/built/admin/assets/{vendor-5c7d7063620bec13668c4370145cd4b4.js → vendor-26cca1d4d56660dc6e915a12ccc3b330.js} +1079 -1032
  62. package/core/built/admin/index.html +7 -7
  63. package/core/cli/generate-data.js +51 -0
  64. package/core/frontend/helpers/ghost_head.js +1 -1
  65. package/core/server/api/endpoints/links.js +34 -1
  66. package/core/server/api/endpoints/members.js +1 -4
  67. package/core/server/api/endpoints/posts-public.js +1 -1
  68. package/core/server/api/endpoints/posts.js +2 -1
  69. package/core/server/api/endpoints/tiers-public.js +2 -14
  70. package/core/server/api/endpoints/tiers.js +5 -51
  71. package/core/server/api/endpoints/utils/serializers/input/posts.js +21 -1
  72. package/core/server/api/endpoints/utils/serializers/input/settings.js +1 -0
  73. package/core/server/api/endpoints/utils/serializers/input/tiers.js +18 -27
  74. package/core/server/api/endpoints/utils/serializers/output/index.js +4 -0
  75. package/core/server/api/endpoints/utils/serializers/output/links.js +5 -0
  76. package/core/server/api/endpoints/utils/serializers/output/mappers/activity-feed-events.js +89 -15
  77. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +20 -6
  78. package/core/server/api/endpoints/utils/serializers/output/mappers/snippets.js +2 -2
  79. package/core/server/api/endpoints/utils/serializers/output/members.js +6 -5
  80. package/core/server/api/endpoints/utils/serializers/output/tiers.js +15 -55
  81. package/core/server/data/db/backup.js +17 -10
  82. package/core/server/data/importer/importers/data/custom-theme-settings.js +81 -0
  83. package/core/server/data/importer/importers/data/data-importer.js +2 -0
  84. package/core/server/data/migrations/utils/permissions.js +35 -24
  85. package/core/server/data/migrations/versions/5.20/2022-10-18-05-39-drop-nullable-tier-id.js +3 -0
  86. package/core/server/data/migrations/versions/5.20/2022-10-18-10-13-add-ghost-subscription-id-column-to-mscs.js +10 -0
  87. package/core/server/data/migrations/versions/5.20/2022-10-19-11-17-add-link-browse-permissions.js +10 -0
  88. package/core/server/data/migrations/versions/5.20/2022-10-20-02-52-add-link-edit-permissions.js +10 -0
  89. package/core/server/data/migrations/versions/5.21/2022-10-24-07-23-disable-feedback-enabled.js +20 -0
  90. package/core/server/data/migrations/versions/5.21/2022-10-25-12-05-backfill-missed-products-columns.js +35 -0
  91. package/core/server/data/migrations/versions/5.21/2022-10-26-04-49-add-batch-id-members-created-events.js +7 -0
  92. package/core/server/data/migrations/versions/5.21/2022-10-26-04-49-add-batch-id-subscription-created-events.js +7 -0
  93. package/core/server/data/migrations/versions/5.21/2022-10-26-04-50-member-subscription-created-batch-id.js +72 -0
  94. package/core/server/data/migrations/versions/5.21/2022-10-26-09-32-add-feedback-enabled-column-to-emails.js +7 -0
  95. package/core/server/data/migrations/versions/5.21/2022-10-27-09-50-add-member-track-source-setting.js +8 -0
  96. package/core/server/data/schema/commands.js +107 -48
  97. package/core/server/data/schema/default-settings/default-settings.json +8 -0
  98. package/core/server/data/schema/fixtures/fixture-manager.js +16 -14
  99. package/core/server/data/schema/fixtures/fixtures.json +14 -2
  100. package/core/server/data/schema/schema.js +7 -3
  101. package/core/server/models/base/plugins/actions.js +1 -1
  102. package/core/server/models/base/plugins/crud.js +12 -0
  103. package/core/server/models/email-recipient.js +14 -0
  104. package/core/server/models/email.js +2 -5
  105. package/core/server/models/member-click-event.js +37 -0
  106. package/core/server/models/member-created-event.js +23 -0
  107. package/core/server/models/member-paid-subscription-event.js +19 -0
  108. package/core/server/models/member.js +6 -0
  109. package/core/server/models/post.js +33 -2
  110. package/core/server/models/redirect.js +1 -0
  111. package/core/server/models/subscription-created-event.js +7 -0
  112. package/core/server/services/audience-feedback/index.js +2 -0
  113. package/core/server/services/link-redirection/LinkRedirectRepository.js +16 -5
  114. package/core/server/services/link-tracking/PostLinkRepository.js +26 -2
  115. package/core/server/services/link-tracking/index.js +3 -1
  116. package/core/server/services/mega/feedback-buttons.js +87 -16
  117. package/core/server/services/mega/mega.js +1 -0
  118. package/core/server/services/mega/template.js +3 -0
  119. package/core/server/services/member-attribution/index.js +3 -1
  120. package/core/server/services/members/api.js +4 -1
  121. package/core/server/services/members/service.js +8 -1
  122. package/core/server/services/newsletters/index.js +3 -1
  123. package/core/server/services/newsletters/service.js +11 -1
  124. package/core/server/services/tiers/TierRepository.js +116 -0
  125. package/core/server/services/tiers/index.js +1 -0
  126. package/core/server/services/tiers/service.js +32 -0
  127. package/core/server/services/url/UrlGenerator.js +4 -2
  128. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  129. package/core/shared/config/defaults.json +1 -1
  130. package/core/shared/labs.js +7 -8
  131. package/ghost.js +1 -0
  132. package/package.json +117 -113
  133. package/yarn.lock +1187 -1129
  134. package/components/tryghost-adapter-manager-5.19.3.tgz +0 -0
  135. package/components/tryghost-api-version-compatibility-service-5.19.3.tgz +0 -0
  136. package/components/tryghost-audience-feedback-5.19.3.tgz +0 -0
  137. package/components/tryghost-bootstrap-socket-5.19.3.tgz +0 -0
  138. package/components/tryghost-constants-5.19.3.tgz +0 -0
  139. package/components/tryghost-custom-theme-settings-service-5.19.3.tgz +0 -0
  140. package/components/tryghost-domain-events-5.19.3.tgz +0 -0
  141. package/components/tryghost-email-analytics-provider-mailgun-5.19.3.tgz +0 -0
  142. package/components/tryghost-email-analytics-service-5.19.3.tgz +0 -0
  143. package/components/tryghost-email-content-generator-5.19.3.tgz +0 -0
  144. package/components/tryghost-express-dynamic-redirects-5.19.3.tgz +0 -0
  145. package/components/tryghost-extract-api-key-5.19.3.tgz +0 -0
  146. package/components/tryghost-html-to-plaintext-5.19.3.tgz +0 -0
  147. package/components/tryghost-link-redirects-5.19.3.tgz +0 -0
  148. package/components/tryghost-link-replacer-5.19.3.tgz +0 -0
  149. package/components/tryghost-link-tracking-5.19.3.tgz +0 -0
  150. package/components/tryghost-magic-link-5.19.3.tgz +0 -0
  151. package/components/tryghost-mailgun-client-5.19.3.tgz +0 -0
  152. package/components/tryghost-member-analytics-service-5.19.3.tgz +0 -0
  153. package/components/tryghost-member-attribution-5.19.3.tgz +0 -0
  154. package/components/tryghost-member-events-5.19.3.tgz +0 -0
  155. package/components/tryghost-members-analytics-ingress-5.19.3.tgz +0 -0
  156. package/components/tryghost-members-api-5.19.3.tgz +0 -0
  157. package/components/tryghost-members-csv-5.19.3.tgz +0 -0
  158. package/components/tryghost-members-events-service-5.19.3.tgz +0 -0
  159. package/components/tryghost-members-importer-5.19.3.tgz +0 -0
  160. package/components/tryghost-members-offers-5.19.3.tgz +0 -0
  161. package/components/tryghost-members-payments-5.19.3.tgz +0 -0
  162. package/components/tryghost-members-stripe-service-5.19.3.tgz +0 -0
  163. package/components/tryghost-minifier-5.19.3.tgz +0 -0
  164. package/components/tryghost-mw-api-version-mismatch-5.19.3.tgz +0 -0
  165. package/components/tryghost-mw-cache-control-5.19.3.tgz +0 -0
  166. package/components/tryghost-mw-error-handler-5.19.3.tgz +0 -0
  167. package/components/tryghost-mw-session-from-token-5.19.3.tgz +0 -0
  168. package/components/tryghost-mw-update-user-last-seen-5.19.3.tgz +0 -0
  169. package/components/tryghost-mw-vhost-5.19.3.tgz +0 -0
  170. package/components/tryghost-oembed-service-5.19.3.tgz +0 -0
  171. package/components/tryghost-referrers-5.19.3.tgz +0 -0
  172. package/components/tryghost-security-5.19.3.tgz +0 -0
  173. package/components/tryghost-session-service-5.19.3.tgz +0 -0
  174. package/components/tryghost-settings-path-manager-5.19.3.tgz +0 -0
  175. package/components/tryghost-staff-service-5.19.3.tgz +0 -0
  176. package/components/tryghost-stats-service-5.19.3.tgz +0 -0
  177. package/components/tryghost-verification-trigger-5.19.3.tgz +0 -0
  178. package/components/tryghost-version-notifications-data-service-5.19.3.tgz +0 -0
  179. package/core/built/admin/assets/ghost-982146a4ada3a5af1981d1919ae01d08.css +0 -1
  180. package/core/built/admin/assets/ghost-dark-41929e4857de411a23597a9de49a4e4f.css +0 -1
@@ -14,20 +14,26 @@ const messages = {
14
14
  noSupportForDatabase: 'No support for database client {client}'
15
15
  };
16
16
 
17
- function addTableColumn(tableName, table, columnName, columnSpec = schema[tableName][columnName]) {
17
+ /**
18
+ * @param {string} tableName
19
+ * @param {import('knex').knex.TableBuilder} tableBuilder
20
+ * @param {string} columnName
21
+ * @param {object} [columnSpec]
22
+ */
23
+ function addTableColumn(tableName, tableBuilder, columnName, columnSpec = schema[tableName][columnName]) {
18
24
  let column;
19
25
 
20
26
  // creation distinguishes between text with fieldtype, string with maxlength and all others
21
27
  if (columnSpec.type === 'text' && Object.prototype.hasOwnProperty.call(columnSpec, 'fieldtype')) {
22
- column = table[columnSpec.type](columnName, columnSpec.fieldtype);
28
+ column = tableBuilder[columnSpec.type](columnName, columnSpec.fieldtype);
23
29
  } else if (columnSpec.type === 'string') {
24
30
  if (Object.prototype.hasOwnProperty.call(columnSpec, 'maxlength')) {
25
- column = table[columnSpec.type](columnName, columnSpec.maxlength);
31
+ column = tableBuilder[columnSpec.type](columnName, columnSpec.maxlength);
26
32
  } else {
27
- column = table[columnSpec.type](columnName, 191);
33
+ column = tableBuilder[columnSpec.type](columnName, 191);
28
34
  }
29
35
  } else {
30
- column = table[columnSpec.type](columnName);
36
+ column = tableBuilder[columnSpec.type](columnName);
31
37
  }
32
38
 
33
39
  if (Object.prototype.hasOwnProperty.call(columnSpec, 'nullable') && columnSpec.nullable === true) {
@@ -48,6 +54,10 @@ function addTableColumn(tableName, table, columnName, columnSpec = schema[tableN
48
54
  // check if table exists?
49
55
  column.references(columnSpec.references);
50
56
  }
57
+ if (Object.prototype.hasOwnProperty.call(columnSpec, 'constraintName')) {
58
+ column.withKeyName(columnSpec.constraintName);
59
+ }
60
+
51
61
  if (Object.prototype.hasOwnProperty.call(columnSpec, 'cascadeDelete') && columnSpec.cascadeDelete === true) {
52
62
  column.onDelete('CASCADE');
53
63
  } else if (Object.prototype.hasOwnProperty.call(columnSpec, 'setNullDelete') && columnSpec.setNullDelete === true) {
@@ -61,18 +71,34 @@ function addTableColumn(tableName, table, columnName, columnSpec = schema[tableN
61
71
  }
62
72
  }
63
73
 
74
+ /**
75
+ * @param {string} tableName
76
+ * @param {string} column
77
+ * @param {import('knex').Knex} [transaction]
78
+ */
64
79
  function setNullable(tableName, column, transaction = db.knex) {
65
80
  return transaction.schema.table(tableName, function (table) {
66
81
  table.setNullable(column);
67
82
  });
68
83
  }
69
84
 
85
+ /**
86
+ * @param {string} tableName
87
+ * @param {string} column
88
+ * @param {import('knex').Knex} [transaction]
89
+ */
70
90
  function dropNullable(tableName, column, transaction = db.knex) {
71
91
  return transaction.schema.table(tableName, function (table) {
72
92
  table.dropNullable(column);
73
93
  });
74
94
  }
75
95
 
96
+ /**
97
+ * @param {string} tableName
98
+ * @param {string} column
99
+ * @param {import('knex').Knex.Transaction} [transaction]
100
+ * @param {object} columnSpec
101
+ */
76
102
  async function addColumn(tableName, column, transaction = db.knex, columnSpec) {
77
103
  const addColumnBuilder = transaction.schema.table(tableName, function (table) {
78
104
  addTableColumn(tableName, table, column, columnSpec);
@@ -85,41 +111,51 @@ async function addColumn(tableName, column, transaction = db.knex, columnSpec) {
85
111
  return;
86
112
  }
87
113
 
88
- let sql = addColumnBuilder.toSQL()[0].sql;
114
+ for (const sqlQuery of addColumnBuilder.toSQL()) {
115
+ let sql = sqlQuery.sql;
89
116
 
90
- if (DatabaseInfo.isMySQL(transaction)) {
91
- // Guard against an ending semicolon
92
- sql = sql.replace(/;\s*$/, '') + ', algorithm=copy';
93
- }
117
+ if (DatabaseInfo.isMySQL(transaction)) {
118
+ // Guard against an ending semicolon
119
+ sql = sql.replace(/;\s*$/, '') + ', algorithm=copy';
120
+ }
94
121
 
95
- await transaction.raw(sql);
122
+ await transaction.raw(sql);
123
+ }
96
124
  }
97
125
 
126
+ /**
127
+ * @param {string} tableName
128
+ * @param {string} column
129
+ * @param {import('knex').Knex} [transaction]
130
+ * @param {object} [columnSpec]
131
+ */
98
132
  async function dropColumn(tableName, column, transaction = db.knex, columnSpec = {}) {
99
133
  if (Object.prototype.hasOwnProperty.call(columnSpec, 'references')) {
100
134
  const [toTable, toColumn] = columnSpec.references.split('.');
101
- await dropForeign({fromTable: tableName, fromColumn: column, toTable, toColumn, transaction});
135
+ await dropForeign({fromTable: tableName, fromColumn: column, toTable, toColumn, constraintName: columnSpec.constraintName, transaction});
102
136
  }
103
137
 
104
- const dropTableBuilder = transaction.schema.table(tableName, function (table) {
138
+ const dropColumnBuilder = transaction.schema.table(tableName, function (table) {
105
139
  table.dropColumn(column);
106
140
  });
107
141
 
108
142
  // Use the default flow for SQLite because .toSQL() is tricky with SQLite when
109
143
  // it does the table dance
110
144
  if (DatabaseInfo.isSQLite(transaction)) {
111
- await dropTableBuilder;
145
+ await dropColumnBuilder;
112
146
  return;
113
147
  }
114
148
 
115
- let sql = dropTableBuilder.toSQL()[0].sql;
149
+ for (const sqlQuery of dropColumnBuilder.toSQL()) {
150
+ let sql = sqlQuery.sql;
116
151
 
117
- if (DatabaseInfo.isMySQL(transaction)) {
118
- // Guard against an ending semicolon
119
- sql = sql.replace(/;\s*$/, '') + ', algorithm=copy';
120
- }
152
+ if (DatabaseInfo.isMySQL(transaction)) {
153
+ // Guard against an ending semicolon
154
+ sql = sql.replace(/;\s*$/, '') + ', algorithm=copy';
155
+ }
121
156
 
122
- await transaction.raw(sql);
157
+ await transaction.raw(sql);
158
+ }
123
159
  }
124
160
 
125
161
  /**
@@ -127,7 +163,7 @@ async function dropColumn(tableName, column, transaction = db.knex, columnSpec =
127
163
  *
128
164
  * @param {string} tableName - name of the table to add unique constraint to
129
165
  * @param {string|string[]} columns - column(s) to form unique constraint with
130
- * @param {import('knex')} transaction - connection object containing knex reference
166
+ * @param {import('knex').Knex} [transaction] - connection object containing knex reference
131
167
  */
132
168
  async function addUnique(tableName, columns, transaction = db.knex) {
133
169
  try {
@@ -154,7 +190,7 @@ async function addUnique(tableName, columns, transaction = db.knex) {
154
190
  *
155
191
  * @param {string} tableName - name of the table to drop unique constraint from
156
192
  * @param {string|string[]} columns - column(s) unique constraint was formed
157
- * @param {import('knex')} transaction - connection object containing knex reference
193
+ * @param {import('knex').Knex} transaction - connection object containing knex reference
158
194
  */
159
195
  async function dropUnique(tableName, columns, transaction = db.knex) {
160
196
  try {
@@ -184,7 +220,7 @@ async function dropUnique(tableName, columns, transaction = db.knex) {
184
220
  * @param {string} configuration.fromColumn - column of the table to add the foreign key to
185
221
  * @param {string} configuration.toTable - name of the table to point the foreign key to
186
222
  * @param {string} configuration.toColumn - column of the table to point the foreign key to
187
- * @param {import('knex')} configuration.transaction - connection object containing knex reference
223
+ * @param {import('knex').Knex} [configuration.transaction] - connection object containing knex reference
188
224
  */
189
225
  async function hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, transaction = db.knex}) {
190
226
  if (!DatabaseInfo.isSQLite(transaction)) {
@@ -208,11 +244,12 @@ async function hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, trans
208
244
  * @param {string} configuration.fromColumn - column of the table to add the foreign key to
209
245
  * @param {string} configuration.toTable - name of the table to point the foreign key to
210
246
  * @param {string} configuration.toColumn - column of the table to point the foreign key to
247
+ * @param {string} [configuration.constraintName] - name of the FK to create
211
248
  * @param {Boolean} [configuration.cascadeDelete] - adds the "on delete cascade" option if true
212
249
  * @param {Boolean} [configuration.setNullDelete] - adds the "on delete SET NULL" option if true
213
- * @param {import('knex')} configuration.transaction - connection object containing knex reference
250
+ * @param {import('knex').Knex} [configuration.transaction] - connection object containing knex reference
214
251
  */
215
- async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDelete = false, setNullDelete = false, transaction = db.knex}) {
252
+ async function addForeign({fromTable, fromColumn, toTable, toColumn, constraintName, cascadeDelete = false, setNullDelete = false, transaction = db.knex}) {
216
253
  if (DatabaseInfo.isSQLite(transaction)) {
217
254
  const foreignKeyExists = await hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, transaction});
218
255
  if (foreignKeyExists) {
@@ -233,12 +270,18 @@ async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDele
233
270
  }
234
271
 
235
272
  await transaction.schema.table(fromTable, function (table) {
273
+ let fkBuilder;
274
+
236
275
  if (cascadeDelete) {
237
- table.foreign(fromColumn).references(`${toTable}.${toColumn}`).onDelete('CASCADE');
276
+ fkBuilder = table.foreign(fromColumn).references(`${toTable}.${toColumn}`).onDelete('CASCADE');
238
277
  } else if (setNullDelete) {
239
- table.foreign(fromColumn).references(`${toTable}.${toColumn}`).onDelete('SET NULL');
278
+ fkBuilder = table.foreign(fromColumn).references(`${toTable}.${toColumn}`).onDelete('SET NULL');
240
279
  } else {
241
- table.foreign(fromColumn).references(`${toTable}.${toColumn}`);
280
+ fkBuilder = table.foreign(fromColumn).references(`${toTable}.${toColumn}`);
281
+ }
282
+
283
+ if (constraintName) {
284
+ fkBuilder.withKeyName(constraintName);
242
285
  }
243
286
  });
244
287
 
@@ -264,9 +307,10 @@ async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDele
264
307
  * @param {string} configuration.fromColumn - column of the table to add the foreign key to
265
308
  * @param {string} configuration.toTable - name of the table to point the foreign key to
266
309
  * @param {string} configuration.toColumn - column of the table to point the foreign key to
267
- * @param {import('knex')} configuration.transaction - connection object containing knex reference
310
+ * @param {string} [configuration.constraintName] - name of the FK to delete
311
+ * @param {import('knex').Knex} [configuration.transaction] - connection object containing knex reference
268
312
  */
269
- async function dropForeign({fromTable, fromColumn, toTable, toColumn, transaction = db.knex}) {
313
+ async function dropForeign({fromTable, fromColumn, toTable, toColumn, constraintName, transaction = db.knex}) {
270
314
  if (DatabaseInfo.isSQLite(transaction)) {
271
315
  const foreignKeyExists = await hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, transaction});
272
316
  if (!foreignKeyExists) {
@@ -287,7 +331,7 @@ async function dropForeign({fromTable, fromColumn, toTable, toColumn, transactio
287
331
  }
288
332
 
289
333
  await transaction.schema.table(fromTable, function (table) {
290
- table.dropForeign(fromColumn);
334
+ table.dropForeign(fromColumn, constraintName);
291
335
  });
292
336
 
293
337
  if (DatabaseInfo.isSQLite(transaction)) {
@@ -308,7 +352,7 @@ async function dropForeign({fromTable, fromColumn, toTable, toColumn, transactio
308
352
  * Checks if primary key index exists in a table over the given columns.
309
353
  *
310
354
  * @param {string} tableName - name of the table to check primary key constraint on
311
- * @param {import('knex')} transaction - connection object containing knex reference
355
+ * @param {import('knex').Knex} [transaction] - connection object containing knex reference
312
356
  */
313
357
  async function hasPrimaryKeySQLite(tableName, transaction = db.knex) {
314
358
  if (!DatabaseInfo.isSQLite(transaction)){
@@ -328,7 +372,7 @@ async function hasPrimaryKeySQLite(tableName, transaction = db.knex) {
328
372
  *
329
373
  * @param {string} tableName - name of the table to add primaykey constraint to
330
374
  * @param {string|string[]} columns - column(s) to form primary key constraint with
331
- * @param {import('knex')} transaction - connection object containing knex reference
375
+ * @param {import('knex').Knex} [transaction] - connection object containing knex reference
332
376
  */
333
377
  async function addPrimaryKey(tableName, columns, transaction = db.knex) {
334
378
  if (DatabaseInfo.isSQLite(transaction)) {
@@ -359,7 +403,7 @@ async function addPrimaryKey(tableName, columns, transaction = db.knex) {
359
403
  * utils if you want that
360
404
  *
361
405
  * @param {String} table - name of the table to create
362
- * @param {import('knex').Transaction} transaction - connection to the DB
406
+ * @param {import('knex').Knex} [transaction] - connection to the DB
363
407
  * @param {Object} [tableSpec] - table schema to generate table with
364
408
  */
365
409
  function createTable(table, transaction = db.knex, tableSpec = schema[table]) {
@@ -377,10 +421,17 @@ function createTable(table, transaction = db.knex, tableSpec = schema[table]) {
377
421
  });
378
422
  }
379
423
 
424
+ /**
425
+ * @param {string} table
426
+ * @param {import('knex').Knex} [transaction] - connection to the DB
427
+ */
380
428
  function deleteTable(table, transaction = db.knex) {
381
429
  return transaction.schema.dropTableIfExists(table);
382
430
  }
383
431
 
432
+ /**
433
+ * @param {import('knex').Knex} [transaction] - connection to the DB
434
+ */
384
435
  function getTables(transaction = db.knex) {
385
436
  const client = transaction.client.config.client;
386
437
 
@@ -391,6 +442,10 @@ function getTables(transaction = db.knex) {
391
442
  return Promise.reject(tpl(messages.noSupportForDatabase, {client: client}));
392
443
  }
393
444
 
445
+ /**
446
+ * @param {string} table
447
+ * @param {import('knex').Knex} [transaction] - connection to the DB
448
+ */
394
449
  function getIndexes(table, transaction = db.knex) {
395
450
  const client = transaction.client.config.client;
396
451
 
@@ -401,6 +456,10 @@ function getIndexes(table, transaction = db.knex) {
401
456
  return Promise.reject(tpl(messages.noSupportForDatabase, {client: client}));
402
457
  }
403
458
 
459
+ /**
460
+ * @param {string} table
461
+ * @param {import('knex').Knex} [transaction] - connection to the DB
462
+ */
404
463
  function getColumns(table, transaction = db.knex) {
405
464
  const client = transaction.client.config.client;
406
465
 
@@ -441,20 +500,20 @@ function createColumnMigration(...migrations) {
441
500
  }
442
501
 
443
502
  module.exports = {
444
- createTable: createTable,
445
- deleteTable: deleteTable,
446
- getTables: getTables,
447
- getIndexes: getIndexes,
448
- addUnique: addUnique,
449
- dropUnique: dropUnique,
450
- addPrimaryKey: addPrimaryKey,
451
- addForeign: addForeign,
452
- dropForeign: dropForeign,
453
- addColumn: addColumn,
454
- dropColumn: dropColumn,
455
- setNullable: setNullable,
456
- dropNullable: dropNullable,
457
- getColumns: getColumns,
503
+ createTable,
504
+ deleteTable,
505
+ getTables,
506
+ getIndexes,
507
+ addUnique,
508
+ dropUnique,
509
+ addPrimaryKey,
510
+ addForeign,
511
+ dropForeign,
512
+ addColumn,
513
+ dropColumn,
514
+ setNullable,
515
+ dropNullable,
516
+ getColumns,
458
517
  createColumnMigration,
459
518
  // NOTE: below are exposed for testing purposes only
460
519
  _hasForeignSQLite: hasForeignSQLite,
@@ -295,6 +295,14 @@
295
295
  "members_yearly_price_id": {
296
296
  "defaultValue": null,
297
297
  "type": "string"
298
+ },
299
+ "members_track_sources": {
300
+ "defaultValue": "true",
301
+ "validations": {
302
+ "isEmpty": false,
303
+ "isIn": [["true", "false"]]
304
+ },
305
+ "type": "boolean"
298
306
  }
299
307
  },
300
308
  "portal": {
@@ -1,5 +1,4 @@
1
1
  const _ = require('lodash');
2
- const Promise = require('bluebird');
3
2
  const logging = require('@tryghost/logging');
4
3
  const {sequence} = require('@tryghost/promise');
5
4
 
@@ -83,16 +82,16 @@ class FixtureManager {
83
82
  const userRolesRelation = this.fixtures.relations.find(r => r.from.relation === 'roles');
84
83
  await this.addFixturesForRelation(userRolesRelation, localOptions);
85
84
 
86
- await Promise.mapSeries(this.fixtures.models.filter(m => !['User', 'Role'].includes(m.name)), (model) => {
85
+ await sequence(this.fixtures.models.filter(m => !['User', 'Role'].includes(m.name)).map(model => () => {
87
86
  logging.info('Model: ' + model.name);
88
87
 
89
88
  return this.addFixturesForModel(model, localOptions);
90
- });
89
+ }));
91
90
 
92
- await Promise.mapSeries(this.fixtures.relations.filter(r => r.from.relation !== 'roles'), (relation) => {
91
+ await sequence(this.fixtures.relations.filter(r => r.from.relation !== 'roles').map(relation => () => {
93
92
  logging.info('Relation: ' + relation.from.model + ' to ' + relation.to.model);
94
93
  return this.addFixturesForRelation(relation, localOptions);
95
- });
94
+ }));
96
95
  }
97
96
 
98
97
  /*
@@ -191,12 +190,15 @@ class FixtureManager {
191
190
  fetchRelationData(relation, options) {
192
191
  const fromOptions = _.extend({}, options, {withRelated: [relation.from.relation]});
193
192
 
194
- const props = {
195
- from: models[relation.from.model].findAll(fromOptions),
196
- to: models[relation.to.model].findAll(options)
197
- };
193
+ const fromRelations = models[relation.from.model].findAll(fromOptions);
194
+ const toRelations = models[relation.to.model].findAll(options);
198
195
 
199
- return Promise.props(props);
196
+ return Promise.all([fromRelations, toRelations]).then(([from, to]) => {
197
+ return {
198
+ from: from,
199
+ to: to
200
+ };
201
+ });
200
202
  }
201
203
 
202
204
  /**
@@ -223,7 +225,7 @@ class FixtureManager {
223
225
  });
224
226
  }
225
227
 
226
- const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
228
+ const results = await sequence(modelFixture.entries.map(entry => async () => {
227
229
  let data = {};
228
230
 
229
231
  // CASE: if id is specified, only query by id
@@ -243,7 +245,7 @@ class FixtureManager {
243
245
  if (!found) {
244
246
  return models[modelFixture.name].add(entry, options);
245
247
  }
246
- });
248
+ }));
247
249
 
248
250
  return {expected: modelFixture.entries.length, done: _.compact(results).length};
249
251
  }
@@ -308,12 +310,12 @@ class FixtureManager {
308
310
  }
309
311
 
310
312
  async removeFixturesForModel(modelFixture, options) {
311
- const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
313
+ const results = await sequence(modelFixture.entries.map(entry => async () => {
312
314
  const found = models[modelFixture.name].findOne(entry.id ? {id: entry.id} : entry, options);
313
315
  if (found) {
314
316
  return models[modelFixture.name].destroy(_.extend(options, {id: found.id}));
315
317
  }
316
- });
318
+ }));
317
319
 
318
320
  return {expected: modelFixture.entries.length, done: results.length};
319
321
  }
@@ -618,6 +618,16 @@
618
618
  "name": "Report comments",
619
619
  "action_type": "report",
620
620
  "object_type": "comment"
621
+ },
622
+ {
623
+ "name": "Browse links",
624
+ "action_type": "browse",
625
+ "object_type": "link"
626
+ },
627
+ {
628
+ "name": "Edit links",
629
+ "action_type": "edit",
630
+ "object_type": "link"
621
631
  }
622
632
  ]
623
633
  },
@@ -747,7 +757,8 @@
747
757
  "members_stripe_connect": "auth",
748
758
  "newsletter": "all",
749
759
  "explore": "read",
750
- "comment": "all"
760
+ "comment": "all",
761
+ "link": "all"
751
762
  },
752
763
  "DB Backup Integration": {
753
764
  "db": "all"
@@ -781,7 +792,8 @@
781
792
  "offer": ["browse", "read", "add", "edit"],
782
793
  "newsletter": ["browse", "read", "add", "edit"],
783
794
  "explore": "read",
784
- "comment": "all"
795
+ "comment": "all",
796
+ "link": "all"
785
797
  },
786
798
  "Editor": {
787
799
  "notification": "all",
@@ -517,7 +517,8 @@ module.exports = {
517
517
  type: 'string', maxlength: 50, nullable: false, validations: {
518
518
  isIn: [['member', 'import', 'system', 'api', 'admin']]
519
519
  }
520
- }
520
+ },
521
+ batch_id: {type: 'string', maxlength: 24, nullable: true}
521
522
  },
522
523
  members_cancel_events: {
523
524
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
@@ -630,7 +631,7 @@ module.exports = {
630
631
  }
631
632
  },
632
633
  member_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'members.id', cascadeDelete: true},
633
- tier_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'products.id'},
634
+ tier_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'products.id'},
634
635
 
635
636
  // These are null if type !== 'paid'
636
637
  cadence: {
@@ -657,6 +658,7 @@ module.exports = {
657
658
  members_stripe_customers_subscriptions: {
658
659
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
659
660
  customer_id: {type: 'string', maxlength: 255, nullable: false, unique: false, references: 'members_stripe_customers.customer_id', cascadeDelete: true},
661
+ ghost_subscription_id: {type: 'string', maxlength: 24, nullable: true, references: 'subscriptions.id', constraintName: 'mscs_ghost_subscription_id_foreign', cascadeDelete: true},
660
662
  subscription_id: {type: 'string', maxlength: 255, nullable: false, unique: true},
661
663
  stripe_price_id: {type: 'string', maxlength: 255, nullable: false, unique: false, index: true, defaultTo: ''},
662
664
  status: {type: 'string', maxlength: 50, nullable: false},
@@ -696,7 +698,8 @@ module.exports = {
696
698
  attribution_url: {type: 'string', maxlength: 2000, nullable: true},
697
699
  referrer_source: {type: 'string', maxlength: 191, nullable: true},
698
700
  referrer_medium: {type: 'string', maxlength: 191, nullable: true},
699
- referrer_url: {type: 'string', maxlength: 2000, nullable: true}
701
+ referrer_url: {type: 'string', maxlength: 2000, nullable: true},
702
+ batch_id: {type: 'string', maxlength: 24, nullable: true}
700
703
  },
701
704
  offer_redemptions: {
702
705
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
@@ -782,6 +785,7 @@ module.exports = {
782
785
  plaintext: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
783
786
  track_opens: {type: 'boolean', nullable: false, defaultTo: false},
784
787
  track_clicks: {type: 'boolean', nullable: false, defaultTo: false},
788
+ feedback_enabled: {type: 'boolean', nullable: false, defaultTo: false},
785
789
  submitted_at: {type: 'dateTime', nullable: false},
786
790
  newsletter_id: {type: 'string', maxlength: 24, nullable: true, references: 'newsletters.id'},
787
791
  created_at: {type: 'dateTime', nullable: false},
@@ -72,7 +72,7 @@ module.exports = function (Bookshelf) {
72
72
  *
73
73
  * We protect adding too many and uncontrolled events.
74
74
  *
75
- * We could embedd adding actions more nicely in the future e.g. plugin.
75
+ * We could embed adding actions more nicely in the future e.g. plugin.
76
76
  */
77
77
  addAction: (model, event, options) => {
78
78
  if (!model.wasChanged()) {
@@ -108,6 +108,18 @@ module.exports = function (Bookshelf) {
108
108
  options.order = this.orderDefaultOptions();
109
109
  }
110
110
 
111
+ if (options.selectRaw) {
112
+ itemCollection.query((qb) => {
113
+ qb.select(qb.client.raw(options.selectRaw));
114
+ });
115
+ }
116
+
117
+ if (options.whereRaw) {
118
+ itemCollection.query((qb) => {
119
+ qb.whereRaw(options.whereRaw);
120
+ });
121
+ }
122
+
111
123
  const response = await itemCollection.fetchPage(options);
112
124
  // Attributes are being filtered here, so they are not leaked into calling layer
113
125
  // where models are serialized to json and do not do more filtering.
@@ -3,6 +3,20 @@ const ghostBookshelf = require('./base');
3
3
  const EmailRecipient = ghostBookshelf.Model.extend({
4
4
  tableName: 'email_recipients',
5
5
  hasTimestamps: false,
6
+
7
+ filterRelations: function filterRelations() {
8
+ return {
9
+ email: {
10
+ // Mongo-knex doesn't support belongsTo relations
11
+ tableName: 'emails',
12
+ tableNameAs: 'email',
13
+ type: 'manyToMany',
14
+ joinTable: 'email_recipients',
15
+ joinFrom: 'id',
16
+ joinTo: 'email_id'
17
+ }
18
+ };
19
+ },
6
20
 
7
21
  email() {
8
22
  return this.belongsTo('Email', 'email_id');
@@ -11,6 +11,7 @@ const Email = ghostBookshelf.Model.extend({
11
11
  recipient_filter: 'status:-free',
12
12
  track_opens: false,
13
13
  track_clicks: false,
14
+ feedback_enabled: false,
14
15
  delivered_count: 0,
15
16
  opened_count: 0,
16
17
  failed_count: 0
@@ -81,11 +82,7 @@ const Email = ghostBookshelf.Model.extend({
81
82
 
82
83
  model.emitChange('deleted', options);
83
84
  }
84
- }, {
85
- post() {
86
- return this.belongsTo('Post');
87
- }
88
- });
85
+ }, {});
89
86
 
90
87
  const Emails = ghostBookshelf.Collection.extend({
91
88
  model: Email
@@ -10,7 +10,31 @@ const MemberClickEvent = ghostBookshelf.Model.extend({
10
10
 
11
11
  member() {
12
12
  return this.belongsTo('Member', 'member_id', 'id');
13
+ },
14
+
15
+ filterExpansions: function filterExpansions() {
16
+ const expansions = [{
17
+ key: 'post_id',
18
+ replacement: 'link.post_id'
19
+ }];
20
+
21
+ return expansions;
22
+ },
23
+
24
+ filterRelations() {
25
+ return {
26
+ link: {
27
+ // Mongo-knex doesn't support belongsTo relations
28
+ tableName: 'redirects',
29
+ tableNameAs: 'link',
30
+ type: 'manyToMany',
31
+ joinTable: 'members_click_events',
32
+ joinFrom: 'id',
33
+ joinTo: 'redirect_id'
34
+ }
35
+ };
13
36
  }
37
+
14
38
  }, {
15
39
  async edit() {
16
40
  throw new errors.IncorrectUsageError({message: 'Cannot edit MemberClickEvent'});
@@ -18,6 +42,19 @@ const MemberClickEvent = ghostBookshelf.Model.extend({
18
42
 
19
43
  async destroy() {
20
44
  throw new errors.IncorrectUsageError({message: 'Cannot destroy MemberClickEvent'});
45
+ },
46
+
47
+ permittedOptions(methodName) {
48
+ let options = ghostBookshelf.Model.permittedOptions.call(this, methodName);
49
+ const validOptions = {
50
+ findPage: ['selectRaw', 'whereRaw']
51
+ };
52
+
53
+ if (validOptions[methodName]) {
54
+ options = options.concat(validOptions[methodName]);
55
+ }
56
+
57
+ return options;
21
58
  }
22
59
  });
23
60
 
@@ -8,6 +8,13 @@ const MemberCreatedEvent = ghostBookshelf.Model.extend({
8
8
  return this.belongsTo('Member', 'member_id', 'id');
9
9
  },
10
10
 
11
+ /**
12
+ * The subscription created event that happend at the same time (if any)
13
+ */
14
+ subscriptionCreatedEvent() {
15
+ return this.belongsTo('SubscriptionCreatedEvent', 'batch_id', 'batch_id');
16
+ },
17
+
11
18
  postAttribution() {
12
19
  return this.belongsTo('Post', 'attribution_id', 'id');
13
20
  },
@@ -18,6 +25,22 @@ const MemberCreatedEvent = ghostBookshelf.Model.extend({
18
25
 
19
26
  tagAttribution() {
20
27
  return this.belongsTo('Tag', 'attribution_id', 'id');
28
+ },
29
+
30
+ filterRelations() {
31
+ return {
32
+ subscriptionCreatedEvent: {
33
+ // Mongo-knex doesn't support belongsTo relations
34
+ tableName: 'members_subscription_created_events',
35
+ tableNameAs: 'subscriptionCreatedEvent',
36
+ type: 'manyToMany',
37
+ joinTable: 'members_created_events',
38
+ joinFrom: 'id',
39
+ joinToForeign: 'batch_id',
40
+ joinTo: 'batch_id',
41
+ joinType: 'leftJoin'
42
+ }
43
+ };
21
44
  }
22
45
  }, {
23
46
  async edit() {