ghost 4.37.0 → 4.38.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.
- package/content/themes/casper/LICENSE +1 -1
- package/content/themes/casper/README.md +1 -1
- package/content/themes/casper/assets/built/global.css +1 -1
- package/content/themes/casper/assets/built/global.css.map +1 -1
- package/content/themes/casper/assets/built/screen.css +1 -1
- package/content/themes/casper/assets/built/screen.css.map +1 -1
- package/content/themes/casper/assets/css/global.css +14 -6
- package/content/themes/casper/assets/css/screen.css +9 -1
- package/content/themes/casper/package.json +2 -2
- package/content/themes/casper/partials/post-card.hbs +1 -1
- package/content/themes/casper/post.hbs +18 -19
- package/content/themes/casper/yarn.lock +186 -217
- package/core/built/assets/ghost-dark-9f760f16230b8bc52e188d6ce28516b0.css +1 -0
- package/core/built/assets/{ghost.min-c1938f6ee696bf08bd6bf93cac341ea2.js → ghost.min-6386b02480494a69c3bfe66206754836.js} +339 -279
- package/core/built/assets/ghost.min-f4c59dd57a2136df8b0a34f87c099034.css +1 -0
- package/core/built/assets/icons/eye.svg +4 -1
- package/core/built/assets/icons/member-add.svg +3 -0
- package/core/built/assets/icons/pin.svg +4 -1
- package/core/built/assets/{vendor.min-6dc30be68238b5c55df0cdc1f2dc8b8d.js → vendor.min-c814d3c4b3f543c4cd5ef3aacd0fc645.js} +36 -34
- package/core/frontend/helpers/get.js +4 -0
- package/core/frontend/helpers/match.js +12 -0
- package/core/frontend/helpers/prev_post.js +11 -1
- package/core/frontend/helpers/tiers.js +59 -0
- package/core/frontend/helpers/tpl/content-cta.hbs +1 -1
- package/core/frontend/services/routing/router-manager.js +1 -1
- package/core/frontend/web/site.js +10 -0
- package/core/server/api/canary/index.js +4 -0
- package/core/server/api/canary/members.js +2 -1
- package/core/server/api/canary/products.js +3 -6
- package/core/server/api/canary/tiers-public.js +34 -0
- package/core/server/api/canary/tiers.js +3 -6
- package/core/server/api/canary/utils/serializers/output/email-posts.js +7 -1
- package/core/server/api/canary/utils/serializers/output/pages.js +9 -2
- package/core/server/api/canary/utils/serializers/output/posts.js +8 -2
- package/core/server/api/canary/utils/serializers/output/preview.js +7 -1
- package/core/server/api/canary/utils/serializers/output/products.js +3 -1
- package/core/server/api/canary/utils/serializers/output/tiers.js +4 -2
- package/core/server/api/canary/utils/serializers/output/utils/mapper.js +17 -7
- package/core/server/data/db/connection.js +3 -2
- package/core/server/data/migrations/versions/3.29/01-remove-duplicate-subscriptions.js +2 -1
- package/core/server/data/migrations/versions/3.29/02-remove-duplicate-customers.js +2 -1
- package/core/server/data/migrations/versions/3.29/03-remove-orphaned-customers.js +2 -1
- package/core/server/data/migrations/versions/3.29/04-remove-orphaned-subscriptions.js +2 -1
- package/core/server/data/migrations/versions/3.29/05-add-member-constraints.js +3 -2
- package/core/server/data/migrations/versions/3.39/06-add-email-recipient-index.js +4 -3
- package/core/server/data/migrations/versions/4.0/14-remove-orphaned-stripe-records.js +2 -1
- package/core/server/data/migrations/versions/4.0/26-add-cascade-on-delete.js +2 -1
- package/core/server/data/migrations/versions/4.0/29-fix-foreign-key-for-members-stripe-customers-subscriptions.js +2 -1
- package/core/server/data/migrations/versions/4.1/02-add-unique-constraint-for-member-stripe-tables.js +2 -1
- package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +3 -2
- package/core/server/data/migrations/versions/4.33/2022-01-18-09-07-remove-duplicate-offer-redemptions.js +2 -2
- package/core/server/data/migrations/versions/4.35/2022-02-01-11-48-update-email-recipient-filter-column-type.js +2 -1
- package/core/server/data/migrations/versions/4.35/2022-02-01-12-03-update-recipient-filter-column-type.js +2 -1
- package/core/server/data/migrations/versions/4.37/2022-02-21-09-53-backfill-members-last-seen-at-column.js +3 -2
- package/core/server/data/migrations/versions/4.38/2022-03-01-08-46-add-visibility-to-tiers.js +11 -0
- package/core/server/data/migrations/versions/4.38/2022-03-03-16-12-add-visibility-to-tiers.js +8 -0
- package/core/server/data/migrations/versions/4.38/2022-03-03-16-17-drop-tiers-visible-column.js +7 -0
- package/core/server/data/schema/clients/index.js +1 -1
- package/core/server/data/schema/clients/mysql.js +4 -4
- package/core/server/data/schema/commands.js +42 -50
- package/core/server/data/schema/fixtures/fixtures.json +4 -2
- package/core/server/data/schema/schema.js +7 -0
- package/core/server/models/product.js +2 -1
- package/core/server/services/auth/api-key/admin.js +15 -6
- package/core/server/services/auth/setup.js +5 -0
- package/core/server/services/email-analytics/lib/event-processor.js +18 -1
- package/core/server/services/members/middleware.js +3 -0
- package/core/server/services/members/service.js +13 -1
- package/core/server/web/admin/views/default-prod.html +4 -4
- package/core/server/web/admin/views/default.html +4 -4
- package/core/server/web/api/app.js +3 -0
- package/core/server/web/api/canary/admin/middleware.js +2 -0
- package/core/server/web/api/canary/content/routes.js +1 -0
- package/core/server/web/members/app.js +1 -1
- package/core/shared/config/defaults.json +2 -2
- package/core/shared/config/utils.js +5 -1
- package/core/shared/labs.js +1 -0
- package/package.json +47 -44
- package/yarn.lock +606 -574
- package/core/built/assets/ghost-dark-d54723f7267e66fa2595f897076e86c2.css +0 -1
- package/core/built/assets/ghost.min-02a5f8954bd85fe28817b8c8b111b8aa.css +0 -1
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
326
|
+
function deleteTable(table, transaction = db.knex) {
|
|
327
|
+
return transaction.schema.dropTableIfExists(table);
|
|
336
328
|
}
|
|
337
329
|
|
|
338
|
-
function getTables(transaction) {
|
|
339
|
-
const 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 =
|
|
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 =
|
|
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 =
|
|
360
|
+
function checkTables(transaction = db.knex) {
|
|
361
|
+
const client = transaction.client.config.client;
|
|
370
362
|
|
|
371
|
-
if (
|
|
363
|
+
if (DatabaseInfo.isMySQL(transaction)) {
|
|
372
364
|
return clients[client].checkPostTable();
|
|
373
365
|
}
|
|
374
366
|
}
|
|
@@ -7,13 +7,15 @@
|
|
|
7
7
|
"name": "Free",
|
|
8
8
|
"slug": "free",
|
|
9
9
|
"type": "free",
|
|
10
|
-
"active": true
|
|
10
|
+
"active": true,
|
|
11
|
+
"visibility": "public"
|
|
11
12
|
},
|
|
12
13
|
{
|
|
13
14
|
"name": "Default Product",
|
|
14
15
|
"slug": "default-product",
|
|
15
16
|
"type": "paid",
|
|
16
|
-
"active": true
|
|
17
|
+
"active": true,
|
|
18
|
+
"visibility": "public"
|
|
17
19
|
}
|
|
18
20
|
]
|
|
19
21
|
},
|
|
@@ -381,6 +381,13 @@ module.exports = {
|
|
|
381
381
|
slug: {type: 'string', maxlength: 191, nullable: false, unique: true},
|
|
382
382
|
active: {type: 'boolean', nullable: false, defaultTo: true},
|
|
383
383
|
welcome_page_url: {type: 'string', maxlength: 2000, nullable: true},
|
|
384
|
+
visibility: {
|
|
385
|
+
type: 'string',
|
|
386
|
+
maxlength: 50,
|
|
387
|
+
nullable: false,
|
|
388
|
+
defaultTo: 'none',
|
|
389
|
+
validations: {isIn: [['public', 'none']]}
|
|
390
|
+
},
|
|
384
391
|
monthly_price_id: {type: 'string', maxlength: 24, nullable: true},
|
|
385
392
|
yearly_price_id: {type: 'string', maxlength: 24, nullable: true},
|
|
386
393
|
description: {type: 'string', maxlength: 191, nullable: true},
|
|
@@ -3,6 +3,7 @@ const url = require('url');
|
|
|
3
3
|
const models = require('../../../models');
|
|
4
4
|
const errors = require('@tryghost/errors');
|
|
5
5
|
const limitService = require('../../../services/limits');
|
|
6
|
+
const config = require('../../../../shared/config');
|
|
6
7
|
const tpl = require('@tryghost/tpl');
|
|
7
8
|
const _ = require('lodash');
|
|
8
9
|
|
|
@@ -139,12 +140,20 @@ const authenticateWithToken = async (req, res, next, {token, JWT_OPTIONS}) => {
|
|
|
139
140
|
const secret = Buffer.from(apiKey.get('secret'), 'hex');
|
|
140
141
|
|
|
141
142
|
const {pathname} = url.parse(req.originalUrl);
|
|
142
|
-
const [hasMatch, version
|
|
143
|
-
|
|
144
|
-
// ensure the token was meant for this api
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
143
|
+
const [hasMatch, version, api] = pathname.match(/ghost\/api\/([^/]+)\/([^/]+)\/(.+)*/); // eslint-disable-line no-unused-vars
|
|
144
|
+
|
|
145
|
+
// ensure the token was meant for this api
|
|
146
|
+
let options;
|
|
147
|
+
if (!config.get('api:versions:all').includes(version)) {
|
|
148
|
+
// CASE: non-versioned api request
|
|
149
|
+
options = Object.assign({
|
|
150
|
+
audience: new RegExp(`\/?${version}\/?$`) // eslint-disable-line no-useless-escape
|
|
151
|
+
}, JWT_OPTIONS);
|
|
152
|
+
} else {
|
|
153
|
+
options = Object.assign({
|
|
154
|
+
audience: new RegExp(`\/?${version}\/${api}\/?$`) // eslint-disable-line no-useless-escape
|
|
155
|
+
}, JWT_OPTIONS);
|
|
156
|
+
}
|
|
148
157
|
|
|
149
158
|
try {
|
|
150
159
|
jwt.verify(token, secret, options);
|
|
@@ -164,6 +164,11 @@ async function installTheme(data, api) {
|
|
|
164
164
|
return data;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
if (themeName.toLowerCase() === 'tryghost/casper') {
|
|
168
|
+
logging.warn('Skipping theme install as Casper is the default theme.');
|
|
169
|
+
return data;
|
|
170
|
+
}
|
|
171
|
+
|
|
167
172
|
// Use the api instead of the services as the api performs extra logic
|
|
168
173
|
try {
|
|
169
174
|
const installResults = await api.themes.install({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const {EventProcessor} = require('@tryghost/email-analytics-service');
|
|
2
|
-
const moment = require('moment');
|
|
2
|
+
const moment = require('moment-timezone');
|
|
3
3
|
|
|
4
4
|
class GhostEventProcessor extends EventProcessor {
|
|
5
5
|
constructor({db}) {
|
|
@@ -88,6 +88,23 @@ class GhostEventProcessor extends EventProcessor {
|
|
|
88
88
|
opened_at: this.db.knex.raw('COALESCE(opened_at, ?)', [moment.utc(event.timestamp).format('YYYY-MM-DD HH:mm:ss')])
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
+
// Using the default timezone set in https://github.com/TryGhost/Ghost/blob/2c5643623db0fc4db390f6997c81a73dca7ccacd/core/server/data/schema/default-settings/default-settings.json#L105
|
|
92
|
+
let timezone = 'Etc/UTC';
|
|
93
|
+
const timezoneData = await this.db.knex('settings').first('value').where('key', 'timezone');
|
|
94
|
+
if (timezoneData && timezoneData.value) {
|
|
95
|
+
timezone = timezoneData.value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
await this.db.knex('members')
|
|
99
|
+
.where('email', '=', event.recipientEmail)
|
|
100
|
+
.andWhere(builder => builder
|
|
101
|
+
.where('last_seen_at', '<', moment.utc(event.timestamp).tz(timezone).startOf('day').utc().format('YYYY-MM-DD HH:mm:ss'))
|
|
102
|
+
.orWhereNull('last_seen_at')
|
|
103
|
+
)
|
|
104
|
+
.update({
|
|
105
|
+
last_seen_at: moment.utc(event.timestamp).format('YYYY-MM-DD HH:mm:ss')
|
|
106
|
+
});
|
|
107
|
+
|
|
91
108
|
return updateResult > 0;
|
|
92
109
|
}
|
|
93
110
|
|
|
@@ -110,9 +110,12 @@ const getPortalProductPrices = async function () {
|
|
|
110
110
|
monthlyPrice: product.monthlyPrice,
|
|
111
111
|
yearlyPrice: product.yearlyPrice,
|
|
112
112
|
benefits: product.benefits,
|
|
113
|
+
active: product.active,
|
|
113
114
|
type: product.type,
|
|
114
115
|
prices: productPrices
|
|
115
116
|
};
|
|
117
|
+
}).filter((product) => {
|
|
118
|
+
return !!product.active;
|
|
116
119
|
});
|
|
117
120
|
const defaultProduct = products.find((product) => {
|
|
118
121
|
return product.type === 'paid';
|
|
@@ -16,6 +16,8 @@ const models = require('../../models');
|
|
|
16
16
|
const {GhostMailer} = require('../mail');
|
|
17
17
|
const jobsService = require('../jobs');
|
|
18
18
|
const VerificationTrigger = require('@tryghost/verification-trigger');
|
|
19
|
+
const DomainEvents = require('@tryghost/domain-events');
|
|
20
|
+
const {LastSeenAtUpdater} = require('@tryghost/members-events-service');
|
|
19
21
|
const events = require('../../lib/common/events');
|
|
20
22
|
|
|
21
23
|
const messages = {
|
|
@@ -139,7 +141,7 @@ module.exports = {
|
|
|
139
141
|
sendVerificationEmail: ({subject, message, amountImported}) => {
|
|
140
142
|
const escalationAddress = config.get('hostSettings:emailVerification:escalationAddress');
|
|
141
143
|
const fromAddress = config.get('user_email');
|
|
142
|
-
|
|
144
|
+
|
|
143
145
|
if (escalationAddress) {
|
|
144
146
|
ghostMailer.send({
|
|
145
147
|
subject,
|
|
@@ -158,6 +160,16 @@ module.exports = {
|
|
|
158
160
|
eventRepository: membersApi.events
|
|
159
161
|
});
|
|
160
162
|
|
|
163
|
+
new LastSeenAtUpdater({
|
|
164
|
+
models: {
|
|
165
|
+
Member: models.Member
|
|
166
|
+
},
|
|
167
|
+
services: {
|
|
168
|
+
domainEvents: DomainEvents,
|
|
169
|
+
settingsCache
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
161
173
|
(async () => {
|
|
162
174
|
try {
|
|
163
175
|
const collection = await models.SingleUseToken.fetchAll();
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Ghost Admin</title>
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.
|
|
11
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.38%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
<link rel="stylesheet" href="assets/vendor.min-2c8ad32b7960bb605ebc20097fee5ebd.css">
|
|
41
|
-
<link rel="stylesheet" href="assets/ghost.min-
|
|
41
|
+
<link rel="stylesheet" href="assets/ghost.min-f4c59dd57a2136df8b0a34f87c099034.css" title="light">
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
<script src="assets/vendor.min-
|
|
60
|
-
<script src="assets/ghost.min-
|
|
59
|
+
<script src="assets/vendor.min-c814d3c4b3f543c4cd5ef3aacd0fc645.js"></script>
|
|
60
|
+
<script src="assets/ghost.min-6386b02480494a69c3bfe66206754836.js"></script>
|
|
61
61
|
|
|
62
62
|
</body>
|
|
63
63
|
</html>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Ghost Admin</title>
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.
|
|
11
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.38%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
<link rel="stylesheet" href="assets/vendor.min-2c8ad32b7960bb605ebc20097fee5ebd.css">
|
|
41
|
-
<link rel="stylesheet" href="assets/ghost.min-
|
|
41
|
+
<link rel="stylesheet" href="assets/ghost.min-f4c59dd57a2136df8b0a34f87c099034.css" title="light">
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
<script src="assets/vendor.min-
|
|
60
|
-
<script src="assets/ghost.min-
|
|
59
|
+
<script src="assets/vendor.min-c814d3c4b3f543c4cd5ef3aacd0fc645.js"></script>
|
|
60
|
+
<script src="assets/ghost.min-6386b02480494a69c3bfe66206754836.js"></script>
|
|
61
61
|
|
|
62
62
|
</body>
|
|
63
63
|
</html>
|
|
@@ -25,6 +25,9 @@ module.exports = function setupApiApp() {
|
|
|
25
25
|
apiApp.lazyUse(urlUtils.getVersionPath({version: 'canary', type: 'content'}), require('./canary/content/app'));
|
|
26
26
|
apiApp.lazyUse(urlUtils.getVersionPath({version: 'canary', type: 'admin'}), require('./canary/admin/app'));
|
|
27
27
|
|
|
28
|
+
apiApp.lazyUse('/content/', require('./canary/content/app'));
|
|
29
|
+
apiApp.lazyUse('/admin/', require('./canary/admin/app'));
|
|
30
|
+
|
|
28
31
|
// Error handling for requests to non-existent API versions
|
|
29
32
|
apiApp.use(errorHandler.resourceNotFound);
|
|
30
33
|
apiApp.use(errorHandler.handleJSONResponse(sentry));
|
|
@@ -34,6 +34,7 @@ module.exports = function apiRoutes() {
|
|
|
34
34
|
router.get('/settings', mw.authenticatePublic, http(api.publicSettings.browse));
|
|
35
35
|
|
|
36
36
|
router.get('/products', mw.authenticatePublic, http(api.productsPublic.browse));
|
|
37
|
+
router.get('/tiers', mw.authenticatePublic, http(api.tiersPublic.browse));
|
|
37
38
|
|
|
38
39
|
return router;
|
|
39
40
|
};
|
|
@@ -39,7 +39,7 @@ module.exports = function setupMembersApp() {
|
|
|
39
39
|
membersApp.get('/api/session', middleware.getIdentityToken);
|
|
40
40
|
membersApp.get('/api/offers/:id', middleware.getOfferData);
|
|
41
41
|
membersApp.delete('/api/session', middleware.deleteSession);
|
|
42
|
-
membersApp.get('/api/site',
|
|
42
|
+
membersApp.get('/api/site', middleware.getMemberSiteData);
|
|
43
43
|
|
|
44
44
|
// NOTE: this is wrapped in a function to ensure we always go via the getter
|
|
45
45
|
membersApp.post('/api/send-magic-link', bodyParser.json(), shared.middleware.brute.membersAuth, (req, res, next) => membersService.api.middleware.sendMagicLink(req, res, next));
|
|
@@ -128,8 +128,8 @@
|
|
|
128
128
|
"emailAnalytics": true
|
|
129
129
|
},
|
|
130
130
|
"portal": {
|
|
131
|
-
"url": "https://unpkg.com/@tryghost/portal@~1.
|
|
132
|
-
"version": "1.
|
|
131
|
+
"url": "https://unpkg.com/@tryghost/portal@~1.15.0/umd/portal.min.js",
|
|
132
|
+
"version": "1.15"
|
|
133
133
|
},
|
|
134
134
|
"tenor": {
|
|
135
135
|
"publicReadOnlyApiKey": null,
|
|
@@ -54,9 +54,13 @@ const checkUrlProtocol = function checkUrlProtocol(url) {
|
|
|
54
54
|
* https://github.com/indexzero/nconf/issues/235#issuecomment-257606507
|
|
55
55
|
*/
|
|
56
56
|
const sanitizeDatabaseProperties = function sanitizeDatabaseProperties(nconf) {
|
|
57
|
+
if (nconf.get('database:client') === 'mysql') {
|
|
58
|
+
nconf.set('database:client', 'mysql2');
|
|
59
|
+
}
|
|
60
|
+
|
|
57
61
|
const database = nconf.get('database');
|
|
58
62
|
|
|
59
|
-
if (nconf.get('database:client') === '
|
|
63
|
+
if (nconf.get('database:client') === 'mysql2') {
|
|
60
64
|
delete database.connection.filename;
|
|
61
65
|
} else {
|
|
62
66
|
delete database.connection.host;
|