ghost 5.9.4 → 5.11.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/components/tryghost-adapter-manager-5.11.0.tgz +0 -0
- package/components/tryghost-api-framework-5.11.0.tgz +0 -0
- package/components/{tryghost-api-version-compatibility-service-0.0.0.tgz → tryghost-api-version-compatibility-service-5.11.0.tgz} +0 -0
- package/components/tryghost-bootstrap-socket-5.11.0.tgz +0 -0
- package/components/tryghost-constants-5.11.0.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.11.0.tgz +0 -0
- package/components/tryghost-domain-events-5.11.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.11.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.11.0.tgz +0 -0
- package/components/tryghost-email-content-generator-5.11.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.11.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.11.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.11.0.tgz +0 -0
- package/components/tryghost-job-manager-5.11.0.tgz +0 -0
- package/components/tryghost-magic-link-5.11.0.tgz +0 -0
- package/components/tryghost-mailgun-client-5.11.0.tgz +0 -0
- package/components/tryghost-member-analytics-service-5.11.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.11.0.tgz +0 -0
- package/components/tryghost-member-events-5.11.0.tgz +0 -0
- package/components/tryghost-members-analytics-ingress-5.11.0.tgz +0 -0
- package/components/tryghost-members-api-5.11.0.tgz +0 -0
- package/components/{tryghost-members-csv-0.0.0.tgz → tryghost-members-csv-5.11.0.tgz} +0 -0
- package/components/tryghost-members-events-service-5.11.0.tgz +0 -0
- package/components/tryghost-members-importer-5.11.0.tgz +0 -0
- package/components/tryghost-members-offers-5.11.0.tgz +0 -0
- package/components/tryghost-members-payments-5.11.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.11.0.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.11.0.tgz +0 -0
- package/components/tryghost-minifier-5.11.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.11.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.11.0.tgz +0 -0
- package/components/tryghost-mw-error-handler-5.11.0.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.11.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.11.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.11.0.tgz +0 -0
- package/components/tryghost-oembed-service-5.11.0.tgz +0 -0
- package/components/tryghost-package-json-5.11.0.tgz +0 -0
- package/components/tryghost-security-5.11.0.tgz +0 -0
- package/components/tryghost-session-service-5.11.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.11.0.tgz +0 -0
- package/components/tryghost-update-check-service-5.11.0.tgz +0 -0
- package/components/tryghost-verification-trigger-5.11.0.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.11.0.tgz +0 -0
- 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/screen.css +8 -5
- package/content/themes/casper/package.json +1 -1
- package/core/boot.js +2 -0
- package/core/bridge.js +2 -0
- package/core/built/admin/assets/chunk.143.14589cc066b8120b73e3.js +49 -0
- package/core/built/admin/assets/{chunk.174.eec7f6398cef4c3e2485.js → chunk.174.ae492405065373dbe102.js} +31 -29
- package/core/built/admin/assets/{chunk.178.506264293194a4922091.js → chunk.178.131e85a10d2031148425.js} +4 -4
- package/core/built/admin/assets/{chunk.351.73f27952f867334a8228.js → chunk.579.65e09dd89eec70d059a0.js} +23 -28
- package/core/built/admin/assets/{chunk.351.73f27952f867334a8228.js.LICENSE.txt → chunk.579.65e09dd89eec70d059a0.js.LICENSE.txt} +0 -0
- package/core/built/admin/assets/ghost-1b0d7c731511bb738ec457d2932c43c0.css +1 -0
- package/core/built/admin/assets/{ghost-b441c9cfa2e31453e86460e50ae7e378.js → ghost-40f5bd12d121c54bbc39e7939e78244f.js} +827 -611
- package/core/built/admin/assets/ghost-dark-7b2825a050b0382630180f48aa78ea5d.css +1 -0
- package/core/built/admin/assets/icons/calendar-stroke.svg +1 -0
- package/core/built/admin/assets/icons/ghost-orb-pink.svg +10 -0
- package/core/built/admin/assets/icons/pen-stroke.svg +1 -0
- package/core/built/admin/assets/img/logos/orb-pink-3-a2c52eb9fda9f2401ea706c3f24976ff.png +0 -0
- package/core/built/admin/assets/{vendor-516c9e43b4aeb92079dc1ab92c9ce492.js → vendor-741dc0e4078e044a0c9bfaad104de8b3.js} +85 -78
- package/core/built/admin/index.html +6 -6
- package/core/frontend/helpers/ghost_head.js +4 -0
- package/core/frontend/helpers/search.js +42 -0
- package/core/frontend/services/member-attribution-assets/index.js +4 -0
- package/core/frontend/services/member-attribution-assets/service.js +83 -0
- package/core/frontend/src/member-attribution/.eslintrc +10 -0
- package/core/frontend/src/member-attribution/member-attribution.js +90 -0
- package/core/frontend/web/site.js +3 -0
- package/core/server/adapters/cache/ImageSizesCacheSyncInMemory.js +7 -0
- package/core/server/adapters/cache/SettingsCacheSyncInMemory.js +7 -0
- package/core/server/api/endpoints/comments-members.js +10 -7
- package/core/server/api/endpoints/invites.js +1 -9
- package/core/server/api/endpoints/labels.js +1 -7
- package/core/server/api/endpoints/members.js +3 -13
- package/core/server/api/endpoints/offers.js +2 -2
- package/core/server/api/endpoints/pages.js +2 -10
- package/core/server/api/endpoints/posts.js +11 -10
- package/core/server/api/endpoints/snippets.js +1 -9
- package/core/server/api/endpoints/tags.js +1 -7
- package/core/server/api/endpoints/utils/serializers/input/pages.js +1 -1
- package/core/server/api/endpoints/utils/serializers/input/posts.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +5 -0
- package/core/server/api/endpoints/utils/serializers/output/members.js +2 -1
- package/core/server/api/endpoints/utils/serializers/output/site.js +1 -0
- package/core/server/api/endpoints/utils/serializers/output/utils/clean.js +6 -7
- package/core/server/api/endpoints/webhooks.js +2 -19
- package/core/server/data/exporter/table-lists.js +2 -0
- package/core/server/data/migrations/versions/5.10/2022-08-15-05-34-add-expiry-at-column-to-members-products.js +6 -0
- package/core/server/data/migrations/versions/5.10/2022-08-16-14-25-add-member-created-events-table.js +11 -0
- package/core/server/data/migrations/versions/5.10/2022-08-16-14-25-add-subscription-created-events-table.js +11 -0
- package/core/server/data/migrations/versions/5.10/2022-08-19-14-15-fix-comments-deletion-strategy.js +45 -0
- package/core/server/data/migrations/versions/5.11/2022-08-22-11-03-add-member-alert-settings-columns-to-users.js +21 -0
- package/core/server/data/migrations/versions/5.11/2022-08-23-13-41-backfill-members-created-events.js +32 -0
- package/core/server/data/migrations/versions/5.11/2022-08-23-13-59-fix-page-resource-type.js +22 -0
- package/core/server/data/schema/fixtures/fixtures.json +3 -0
- package/core/server/data/schema/schema.js +24 -2
- package/core/server/lib/image/cached-image-size-from-url.js +52 -28
- package/core/server/lib/image/gravatar.js +8 -7
- package/core/server/lib/image/image-size.js +60 -56
- package/core/server/lib/image/image-utils.js +5 -2
- package/core/server/lib/image/index.js +14 -1
- package/core/server/models/action.js +0 -10
- package/core/server/models/api-key.js +3 -18
- package/core/server/models/base/plugins/actions.js +55 -0
- package/core/server/models/integration.js +3 -0
- package/core/server/models/label.js +3 -18
- package/core/server/models/member-created-event.js +26 -0
- package/core/server/models/member.js +54 -4
- package/core/server/models/offer.js +3 -0
- package/core/server/models/post.js +25 -18
- package/core/server/models/product.js +3 -0
- package/core/server/models/settings.js +4 -0
- package/core/server/models/subscription-created-event.js +30 -0
- package/core/server/models/tag.js +3 -18
- package/core/server/models/user.js +7 -19
- package/core/server/models/webhook.js +3 -0
- package/core/server/services/auth/api-key/admin.js +0 -3
- package/core/server/services/auth/passwordreset.js +0 -3
- package/core/server/services/comments/emails.js +3 -3
- package/core/server/services/explore/service.js +8 -6
- package/core/server/services/member-attribution/index.js +52 -0
- package/core/server/services/members/api.js +3 -1
- package/core/server/services/members/jobs/clean-expired-comped.js +105 -0
- package/core/server/services/members/jobs/index.js +27 -0
- package/core/server/services/members/service.js +14 -8
- package/core/server/services/public-config/site.js +1 -0
- package/core/server/services/route-settings/default-settings-manager.js +19 -17
- package/core/server/services/settings/settings-service.js +1 -1
- package/core/server/services/webhooks/trigger.js +14 -5
- package/core/shared/config/defaults.json +8 -3
- package/core/shared/labs.js +5 -2
- package/package.json +84 -83
- package/yarn.lock +440 -615
- package/components/tryghost-adapter-manager-0.0.0.tgz +0 -0
- package/components/tryghost-api-framework-0.0.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-0.0.0.tgz +0 -0
- package/components/tryghost-constants-0.0.0.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-0.0.0.tgz +0 -0
- package/components/tryghost-domain-events-0.0.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-0.0.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-0.0.0.tgz +0 -0
- package/components/tryghost-email-content-generator-0.0.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-0.0.0.tgz +0 -0
- package/components/tryghost-extract-api-key-0.0.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-0.0.0.tgz +0 -0
- package/components/tryghost-job-manager-0.0.0.tgz +0 -0
- package/components/tryghost-magic-link-0.0.0.tgz +0 -0
- package/components/tryghost-mailgun-client-0.0.0.tgz +0 -0
- package/components/tryghost-member-analytics-service-0.0.0.tgz +0 -0
- package/components/tryghost-member-events-0.0.0.tgz +0 -0
- package/components/tryghost-members-analytics-ingress-0.0.0.tgz +0 -0
- package/components/tryghost-members-api-0.0.0.tgz +0 -0
- package/components/tryghost-members-events-service-0.0.0.tgz +0 -0
- package/components/tryghost-members-importer-0.0.0.tgz +0 -0
- package/components/tryghost-members-offers-0.0.0.tgz +0 -0
- package/components/tryghost-members-payments-0.0.0.tgz +0 -0
- package/components/tryghost-members-ssr-0.0.0.tgz +0 -0
- package/components/tryghost-members-stripe-service-0.0.0.tgz +0 -0
- package/components/tryghost-minifier-0.0.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-0.0.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-0.0.0.tgz +0 -0
- package/components/tryghost-mw-error-handler-0.0.0.tgz +0 -0
- package/components/tryghost-mw-session-from-token-0.0.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-0.0.0.tgz +0 -0
- package/components/tryghost-mw-vhost-0.0.0.tgz +0 -0
- package/components/tryghost-oembed-service-0.0.0.tgz +0 -0
- package/components/tryghost-package-json-0.0.0.tgz +0 -0
- package/components/tryghost-security-0.0.0.tgz +0 -0
- package/components/tryghost-session-service-0.0.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-0.0.0.tgz +0 -0
- package/components/tryghost-update-check-service-0.0.0.tgz +0 -0
- package/components/tryghost-verification-trigger-0.0.0.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-0.0.0.tgz +0 -0
- package/core/built/admin/assets/chunk.143.1c158e8ef19f10e5439c.js +0 -41
- package/core/built/admin/assets/ghost-dark-4080c8f100997d4b8947f5da0e7946a1.css +0 -1
- package/core/built/admin/assets/ghost-facfdf4a7d9759c5b681340805f21fd8.css +0 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const {addTable} = require('../../utils');
|
|
2
|
+
|
|
3
|
+
module.exports = addTable('members_created_events', {
|
|
4
|
+
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
5
|
+
created_at: {type: 'dateTime', nullable: false},
|
|
6
|
+
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
|
|
7
|
+
attribution_id: {type: 'string', maxlength: 24, nullable: true},
|
|
8
|
+
attribution_type: {type: 'string', maxlength: 50, nullable: true},
|
|
9
|
+
attribution_url: {type: 'string', maxlength: 2000, nullable: true},
|
|
10
|
+
source: {type: 'string', maxlength: 50, nullable: false}
|
|
11
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const {addTable} = require('../../utils');
|
|
2
|
+
|
|
3
|
+
module.exports = addTable('members_subscription_created_events', {
|
|
4
|
+
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
5
|
+
created_at: {type: 'dateTime', nullable: false},
|
|
6
|
+
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
|
|
7
|
+
subscription_id: {type: 'string', maxlength: 24, nullable: false, references: 'members_stripe_customers_subscriptions.id', cascadeDelete: true},
|
|
8
|
+
attribution_id: {type: 'string', maxlength: 24, nullable: true},
|
|
9
|
+
attribution_type: {type: 'string', maxlength: 50, nullable: true},
|
|
10
|
+
attribution_url: {type: 'string', maxlength: 2000, nullable: true}
|
|
11
|
+
});
|
package/core/server/data/migrations/versions/5.10/2022-08-19-14-15-fix-comments-deletion-strategy.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const {addForeign, dropForeign} = require('../../../schema/commands');
|
|
2
|
+
const logging = require('@tryghost/logging');
|
|
3
|
+
const {createTransactionalMigration} = require('../../utils');
|
|
4
|
+
|
|
5
|
+
module.exports = createTransactionalMigration(
|
|
6
|
+
async function up(knex) {
|
|
7
|
+
logging.info('Adding on delete CASCADE for comments parent_id');
|
|
8
|
+
|
|
9
|
+
await dropForeign({
|
|
10
|
+
fromTable: 'comments',
|
|
11
|
+
fromColumn: 'parent_id',
|
|
12
|
+
toTable: 'comments',
|
|
13
|
+
toColumn: 'id',
|
|
14
|
+
transaction: knex
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
await addForeign({
|
|
18
|
+
fromTable: 'comments',
|
|
19
|
+
fromColumn: 'parent_id',
|
|
20
|
+
toTable: 'comments',
|
|
21
|
+
toColumn: 'id',
|
|
22
|
+
cascadeDelete: true,
|
|
23
|
+
transaction: knex
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
async function down(knex) {
|
|
27
|
+
logging.info('Restoring foreign key for comments parent_id');
|
|
28
|
+
|
|
29
|
+
await dropForeign({
|
|
30
|
+
fromTable: 'comments',
|
|
31
|
+
fromColumn: 'parent_id',
|
|
32
|
+
toTable: 'comments',
|
|
33
|
+
toColumn: 'id',
|
|
34
|
+
transaction: knex
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await addForeign({
|
|
38
|
+
fromTable: 'comments',
|
|
39
|
+
fromColumn: 'parent_id',
|
|
40
|
+
toTable: 'comments',
|
|
41
|
+
toColumn: 'id',
|
|
42
|
+
transaction: knex
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const {createAddColumnMigration, combineNonTransactionalMigrations} = require('../../utils');
|
|
2
|
+
|
|
3
|
+
module.exports = combineNonTransactionalMigrations(
|
|
4
|
+
createAddColumnMigration('users', 'free_member_signup_notification', {
|
|
5
|
+
type: 'boolean',
|
|
6
|
+
nullable: false,
|
|
7
|
+
defaultTo: true
|
|
8
|
+
}),
|
|
9
|
+
|
|
10
|
+
createAddColumnMigration('users', 'paid_subscription_canceled_notification', {
|
|
11
|
+
type: 'boolean',
|
|
12
|
+
nullable: false,
|
|
13
|
+
defaultTo: false
|
|
14
|
+
}),
|
|
15
|
+
|
|
16
|
+
createAddColumnMigration('users', 'paid_subscription_started_notification', {
|
|
17
|
+
type: 'boolean',
|
|
18
|
+
nullable: false,
|
|
19
|
+
defaultTo: true
|
|
20
|
+
})
|
|
21
|
+
);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const ObjectID = require('bson-objectid').default;
|
|
2
|
+
const logging = require('@tryghost/logging');
|
|
3
|
+
|
|
4
|
+
const {createTransactionalMigration} = require('../../utils');
|
|
5
|
+
|
|
6
|
+
module.exports = createTransactionalMigration(
|
|
7
|
+
async function up(knex) {
|
|
8
|
+
const members = await knex('members')
|
|
9
|
+
.select('id', 'created_at');
|
|
10
|
+
|
|
11
|
+
if (members.length === 0) {
|
|
12
|
+
logging.warn(`Skipping migration because no members found`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const toInsert = members.map((member) => {
|
|
17
|
+
return {
|
|
18
|
+
id: ObjectID().toHexString(),
|
|
19
|
+
member_id: member.id,
|
|
20
|
+
created_at: member.created_at,
|
|
21
|
+
source: 'member'
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
logging.info(`Inserting ${toInsert.length} members created events`);
|
|
26
|
+
await knex.batchInsert('members_created_events', toInsert);
|
|
27
|
+
},
|
|
28
|
+
async function down(knex) {
|
|
29
|
+
logging.info(`Clearing all members created events`);
|
|
30
|
+
await knex('members_created_events').del();
|
|
31
|
+
}
|
|
32
|
+
);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const logging = require('@tryghost/logging');
|
|
2
|
+
const {createTransactionalMigration} = require('../../utils');
|
|
3
|
+
|
|
4
|
+
module.exports = createTransactionalMigration(
|
|
5
|
+
async function up(knex) {
|
|
6
|
+
logging.info(`Changing Action event 'page' resource_type to 'post'`);
|
|
7
|
+
|
|
8
|
+
const affectedRows = await knex('actions')
|
|
9
|
+
.update({
|
|
10
|
+
resource_type: 'post',
|
|
11
|
+
context: JSON.stringify({
|
|
12
|
+
type: 'page'
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
.where('resource_type', 'page');
|
|
16
|
+
|
|
17
|
+
logging.info(`Updated ${affectedRows} Action events from 'page' to 'post'`);
|
|
18
|
+
},
|
|
19
|
+
async function down() {
|
|
20
|
+
// no-op: we don't want to put `pages` back as a resource type
|
|
21
|
+
}
|
|
22
|
+
);
|
|
@@ -626,6 +626,9 @@
|
|
|
626
626
|
"name": "Ghost",
|
|
627
627
|
"email": "ghost@example.com",
|
|
628
628
|
"status": "inactive",
|
|
629
|
+
"free_member_signup_notification": true,
|
|
630
|
+
"paid_subscription_started_notification": true,
|
|
631
|
+
"paid_subscription_canceled_notification": false,
|
|
629
632
|
"roles": []
|
|
630
633
|
}
|
|
631
634
|
]
|
|
@@ -149,6 +149,9 @@ module.exports = {
|
|
|
149
149
|
tour: {type: 'text', maxlength: 65535, nullable: true},
|
|
150
150
|
last_seen: {type: 'dateTime', nullable: true},
|
|
151
151
|
comment_notifications: {type: 'boolean', nullable: false, defaultTo: true},
|
|
152
|
+
free_member_signup_notification: {type: 'boolean', nullable: false, defaultTo: true},
|
|
153
|
+
paid_subscription_started_notification: {type: 'boolean', nullable: false, defaultTo: true},
|
|
154
|
+
paid_subscription_canceled_notification: {type: 'boolean', nullable: false, defaultTo: false},
|
|
152
155
|
created_at: {type: 'dateTime', nullable: false},
|
|
153
156
|
created_by: {type: 'string', maxlength: 24, nullable: false},
|
|
154
157
|
updated_at: {type: 'dateTime', nullable: true},
|
|
@@ -466,7 +469,8 @@ module.exports = {
|
|
|
466
469
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
467
470
|
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
|
|
468
471
|
product_id: {type: 'string', maxlength: 24, nullable: false, references: 'products.id', cascadeDelete: true},
|
|
469
|
-
sort_order: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0}
|
|
472
|
+
sort_order: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0},
|
|
473
|
+
expiry_at: {type: 'dateTime', nullable: true}
|
|
470
474
|
},
|
|
471
475
|
posts_products: {
|
|
472
476
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
@@ -474,6 +478,15 @@ module.exports = {
|
|
|
474
478
|
product_id: {type: 'string', maxlength: 24, nullable: false, references: 'products.id', cascadeDelete: true},
|
|
475
479
|
sort_order: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0}
|
|
476
480
|
},
|
|
481
|
+
members_created_events: {
|
|
482
|
+
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
483
|
+
created_at: {type: 'dateTime', nullable: false},
|
|
484
|
+
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
|
|
485
|
+
attribution_id: {type: 'string', maxlength: 24, nullable: true},
|
|
486
|
+
attribution_type: {type: 'string', maxlength: 50, nullable: true},
|
|
487
|
+
attribution_url: {type: 'string', maxlength: 2000, nullable: true},
|
|
488
|
+
source: {type: 'string', maxlength: 50, nullable: false}
|
|
489
|
+
},
|
|
477
490
|
members_cancel_events: {
|
|
478
491
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
479
492
|
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
|
|
@@ -594,6 +607,15 @@ module.exports = {
|
|
|
594
607
|
plan_amount: {type: 'integer', nullable: false},
|
|
595
608
|
plan_currency: {type: 'string', maxLength: 3, nullable: false}
|
|
596
609
|
},
|
|
610
|
+
members_subscription_created_events: {
|
|
611
|
+
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
612
|
+
created_at: {type: 'dateTime', nullable: false},
|
|
613
|
+
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
|
|
614
|
+
subscription_id: {type: 'string', maxlength: 24, nullable: false, references: 'members_stripe_customers_subscriptions.id', cascadeDelete: true},
|
|
615
|
+
attribution_id: {type: 'string', maxlength: 24, nullable: true},
|
|
616
|
+
attribution_type: {type: 'string', maxlength: 50, nullable: true},
|
|
617
|
+
attribution_url: {type: 'string', maxlength: 2000, nullable: true}
|
|
618
|
+
},
|
|
597
619
|
offer_redemptions: {
|
|
598
620
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
599
621
|
offer_id: {type: 'string', maxlength: 24, nullable: false, references: 'offers.id', cascadeDelete: true},
|
|
@@ -754,7 +776,7 @@ module.exports = {
|
|
|
754
776
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
755
777
|
post_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'posts.id', cascadeDelete: true},
|
|
756
778
|
member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id', setNullDelete: true},
|
|
757
|
-
parent_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'comments.id'},
|
|
779
|
+
parent_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'comments.id', cascadeDelete: true},
|
|
758
780
|
status: {type: 'string', maxlength: 50, nullable: false, defaultTo: 'published', validations: {isIn: [['published', 'hidden', 'deleted']]}},
|
|
759
781
|
html: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
|
|
760
782
|
edited_at: {type: 'dateTime', nullable: true},
|
|
@@ -1,52 +1,76 @@
|
|
|
1
1
|
const debug = require('@tryghost/debug')('utils:image-size-cache');
|
|
2
2
|
const errors = require('@tryghost/errors');
|
|
3
3
|
const logging = require('@tryghost/logging');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @example
|
|
7
|
+
* {
|
|
8
|
+
* height: 50,
|
|
9
|
+
* url: 'https://mysite.com/images/cat.jpg',
|
|
10
|
+
* width: 50
|
|
11
|
+
* }
|
|
12
|
+
* @typedef ImageSizeCache
|
|
13
|
+
* @type {Object}
|
|
14
|
+
* @property {string} url image url
|
|
15
|
+
* @property {number} height image height
|
|
16
|
+
* @property {number} width image width
|
|
17
|
+
*/
|
|
18
|
+
|
|
4
19
|
class CachedImageSizeFromUrl {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} options
|
|
23
|
+
* @param {(url: string) => Promise<ImageSizeCache>} options.getImageSizeFromUrl - method that resolves images based on URL
|
|
24
|
+
* @param {Object} options.cache - cache store instance
|
|
25
|
+
*/
|
|
26
|
+
constructor({getImageSizeFromUrl, cache}) {
|
|
27
|
+
this.getImageSizeFromUrl = getImageSizeFromUrl;
|
|
28
|
+
this.cache = cache;
|
|
8
29
|
}
|
|
9
30
|
|
|
10
31
|
/**
|
|
11
32
|
* Get cached image size from URL
|
|
12
33
|
* Always returns {object} imageSizeCache
|
|
13
34
|
* @param {string} url
|
|
14
|
-
* @returns {Promise<
|
|
35
|
+
* @returns {Promise<ImageSizeCache>}
|
|
15
36
|
* @description Takes a url and returns image width and height from cache if available.
|
|
16
37
|
* If not in cache, `getImageSizeFromUrl` is called and returns the dimensions in a Promise.
|
|
17
38
|
*/
|
|
18
|
-
getCachedImageSizeFromUrl(url) {
|
|
39
|
+
async getCachedImageSizeFromUrl(url) {
|
|
19
40
|
if (!url || url === undefined || url === null) {
|
|
20
41
|
return;
|
|
21
42
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
43
|
+
|
|
44
|
+
const cachedImageSize = await this.cache.get(url);
|
|
45
|
+
|
|
46
|
+
if (cachedImageSize) {
|
|
47
|
+
debug('Read image from cache:', url);
|
|
48
|
+
|
|
49
|
+
return cachedImageSize;
|
|
50
|
+
} else {
|
|
51
|
+
try {
|
|
52
|
+
const res = await this.getImageSizeFromUrl(url);
|
|
53
|
+
await this.cache.set(url, res);
|
|
54
|
+
|
|
28
55
|
debug('Cached image:', url);
|
|
29
|
-
|
|
30
|
-
return this.cache.get(url);
|
|
31
|
-
}).catch(errors.NotFoundError, () => {
|
|
32
|
-
debug('Cached image (not found):', url);
|
|
33
|
-
// in case of error we just attach the url
|
|
34
|
-
this.cache.set(url, url);
|
|
35
|
-
|
|
56
|
+
|
|
36
57
|
return this.cache.get(url);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (err instanceof errors.NotFoundError) {
|
|
60
|
+
debug('Cached image (not found):', url);
|
|
61
|
+
} else {
|
|
62
|
+
debug('Cached image (error):', url);
|
|
63
|
+
logging.error(err);
|
|
64
|
+
}
|
|
65
|
+
|
|
41
66
|
// in case of error we just attach the url
|
|
42
|
-
this.cache.set(url,
|
|
43
|
-
|
|
67
|
+
await this.cache.set(url, {
|
|
68
|
+
url
|
|
69
|
+
});
|
|
70
|
+
|
|
44
71
|
return this.cache.get(url);
|
|
45
|
-
}
|
|
72
|
+
}
|
|
46
73
|
}
|
|
47
|
-
debug('Read image from cache:', url);
|
|
48
|
-
// returns image size from cache
|
|
49
|
-
return this.cache.get(url);
|
|
50
74
|
}
|
|
51
75
|
}
|
|
52
76
|
|
|
@@ -40,15 +40,16 @@ class Gravatar {
|
|
|
40
40
|
image: imageUrl
|
|
41
41
|
};
|
|
42
42
|
})
|
|
43
|
-
.catch(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
.catch(function (err) {
|
|
44
|
+
if (err.statusCode === 404) {
|
|
45
|
+
return {
|
|
46
|
+
image: undefined
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
49
50
|
// ignore error, just resolve with no image url
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
module.exports = Gravatar;
|
|
55
|
+
module.exports = Gravatar;
|
|
@@ -163,39 +163,41 @@ class ImageSize {
|
|
|
163
163
|
width: dimensions.width,
|
|
164
164
|
height: dimensions.height
|
|
165
165
|
};
|
|
166
|
-
}).catch(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
166
|
+
}).catch((err) => {
|
|
167
|
+
if (err.code === 'URL_MISSING_INVALID') {
|
|
168
|
+
return Promise.reject(new errors.InternalServerError({
|
|
169
|
+
message: err.message,
|
|
170
|
+
code: 'IMAGE_SIZE_URL',
|
|
171
|
+
statusCode: err.statusCode,
|
|
172
|
+
context: err.url || imagePath
|
|
173
|
+
}));
|
|
174
|
+
} else if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT' || err.code === 'ECONNRESET' || err.statusCode === 408) {
|
|
175
|
+
return Promise.reject(new errors.InternalServerError({
|
|
176
|
+
message: 'Request timed out.',
|
|
177
|
+
code: 'IMAGE_SIZE_URL',
|
|
178
|
+
statusCode: err.statusCode,
|
|
179
|
+
context: err.url || imagePath
|
|
180
|
+
}));
|
|
181
|
+
} else if (err.code === 'ENOENT' || err.code === 'ENOTFOUND' || err.statusCode === 404) {
|
|
182
|
+
return Promise.reject(new errors.NotFoundError({
|
|
183
|
+
message: 'Image not found.',
|
|
184
|
+
code: 'IMAGE_SIZE_URL',
|
|
185
|
+
statusCode: err.statusCode,
|
|
186
|
+
context: err.url || imagePath
|
|
187
|
+
}));
|
|
188
|
+
} else {
|
|
189
|
+
if (errors.utils.isGhostError(err)) {
|
|
190
|
+
return Promise.reject(err);
|
|
191
|
+
}
|
|
191
192
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
return Promise.reject(new errors.InternalServerError({
|
|
194
|
+
message: 'Unknown Request error.',
|
|
195
|
+
code: 'IMAGE_SIZE_URL',
|
|
196
|
+
statusCode: err.statusCode,
|
|
197
|
+
context: err.url || imagePath,
|
|
198
|
+
err: err
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
199
201
|
});
|
|
200
202
|
}
|
|
201
203
|
|
|
@@ -237,32 +239,34 @@ class ImageSize {
|
|
|
237
239
|
height: dimensions.height
|
|
238
240
|
};
|
|
239
241
|
})
|
|
240
|
-
.catch(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
242
|
+
.catch((err) => {
|
|
243
|
+
if (err.code === 'ENOENT') {
|
|
244
|
+
return Promise.reject(new errors.NotFoundError({
|
|
245
|
+
message: err.message,
|
|
246
|
+
code: 'IMAGE_SIZE_STORAGE',
|
|
247
|
+
err: err,
|
|
248
|
+
context: filePath,
|
|
249
|
+
errorDetails: {
|
|
250
|
+
originalPath: imagePath,
|
|
251
|
+
reqFilePath: filePath
|
|
252
|
+
}
|
|
253
|
+
}));
|
|
254
|
+
} else {
|
|
255
|
+
if (errors.utils.isGhostError(err)) {
|
|
256
|
+
return Promise.reject(err);
|
|
249
257
|
}
|
|
250
|
-
}));
|
|
251
|
-
}).catch((err) => {
|
|
252
|
-
if (errors.utils.isGhostError(err)) {
|
|
253
|
-
return Promise.reject(err);
|
|
254
|
-
}
|
|
255
258
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
259
|
+
return Promise.reject(new errors.InternalServerError({
|
|
260
|
+
message: err.message,
|
|
261
|
+
code: 'IMAGE_SIZE_STORAGE',
|
|
262
|
+
err: err,
|
|
263
|
+
context: filePath,
|
|
264
|
+
errorDetails: {
|
|
265
|
+
originalPath: imagePath,
|
|
266
|
+
reqFilePath: filePath
|
|
267
|
+
}
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
266
270
|
});
|
|
267
271
|
}
|
|
268
272
|
|
|
@@ -4,10 +4,13 @@ const Gravatar = require('./gravatar');
|
|
|
4
4
|
const ImageSize = require('./image-size');
|
|
5
5
|
|
|
6
6
|
class ImageUtils {
|
|
7
|
-
constructor({config, urlUtils, settingsCache, storageUtils, storage, validator, request}) {
|
|
7
|
+
constructor({config, urlUtils, settingsCache, storageUtils, storage, validator, request, cacheStore}) {
|
|
8
8
|
this.blogIcon = new BlogIcon({config, urlUtils, settingsCache, storageUtils});
|
|
9
9
|
this.imageSize = new ImageSize({config, storage, storageUtils, validator, urlUtils, request});
|
|
10
|
-
this.cachedImageSizeFromUrl = new CachedImageSizeFromUrl({
|
|
10
|
+
this.cachedImageSizeFromUrl = new CachedImageSizeFromUrl({
|
|
11
|
+
getImageSizeFromUrl: this.imageSize.getImageSizeFromUrl.bind(this.imageSize),
|
|
12
|
+
cache: cacheStore
|
|
13
|
+
});
|
|
11
14
|
this.gravatar = new Gravatar({config, request});
|
|
12
15
|
}
|
|
13
16
|
}
|
|
@@ -7,4 +7,17 @@ const config = require('../../../shared/config');
|
|
|
7
7
|
const settingsCache = require('../../../shared/settings-cache');
|
|
8
8
|
const ImageUtils = require('./image-utils');
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
const adapterManager = require('../../services/adapter-manager');
|
|
11
|
+
|
|
12
|
+
const cacheStore = adapterManager.getAdapter('cache:imageSizes');
|
|
13
|
+
|
|
14
|
+
module.exports = new ImageUtils({
|
|
15
|
+
config,
|
|
16
|
+
urlUtils,
|
|
17
|
+
settingsCache,
|
|
18
|
+
storageUtils,
|
|
19
|
+
storage,
|
|
20
|
+
validator,
|
|
21
|
+
request,
|
|
22
|
+
cacheStore
|
|
23
|
+
});
|
|
@@ -19,16 +19,6 @@ const Action = ghostBookshelf.Model.extend({
|
|
|
19
19
|
|
|
20
20
|
resource() {
|
|
21
21
|
return this.morphTo('resource', ['resource_type', 'resource_id'], ...candidates);
|
|
22
|
-
},
|
|
23
|
-
|
|
24
|
-
toJSON(unfilteredOptions) {
|
|
25
|
-
const options = Action.filterOptions(unfilteredOptions, 'toJSON');
|
|
26
|
-
const attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options);
|
|
27
|
-
|
|
28
|
-
// @TODO: context is not implemented yet
|
|
29
|
-
delete attrs.context;
|
|
30
|
-
|
|
31
|
-
return attrs;
|
|
32
22
|
}
|
|
33
23
|
}, {
|
|
34
24
|
orderDefaultOptions: function orderDefaultOptions() {
|
|
@@ -6,6 +6,9 @@ const {Role} = require('./role');
|
|
|
6
6
|
const ApiKey = ghostBookshelf.Model.extend({
|
|
7
7
|
tableName: 'api_keys',
|
|
8
8
|
|
|
9
|
+
actionsCollectCRUD: true,
|
|
10
|
+
actionsResourceType: 'api_key',
|
|
11
|
+
|
|
9
12
|
defaults() {
|
|
10
13
|
const secret = security.secret.create(this.get('type'));
|
|
11
14
|
|
|
@@ -53,24 +56,6 @@ const ApiKey = ghostBookshelf.Model.extend({
|
|
|
53
56
|
if (this.previous('secret') !== this.get('secret')) {
|
|
54
57
|
this.addAction(model, 'refreshed', options);
|
|
55
58
|
}
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
getAction(event, options) {
|
|
59
|
-
const actor = this.getActor(options);
|
|
60
|
-
|
|
61
|
-
// @NOTE: we ignore internal updates (`options.context.internal`) for now
|
|
62
|
-
if (!actor) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// @TODO: implement context
|
|
67
|
-
return {
|
|
68
|
-
event: event,
|
|
69
|
-
resource_id: this.id || this.previous('id'),
|
|
70
|
-
resource_type: 'api_key',
|
|
71
|
-
actor_id: actor.id,
|
|
72
|
-
actor_type: actor.type
|
|
73
|
-
};
|
|
74
59
|
}
|
|
75
60
|
}, {
|
|
76
61
|
refreshSecret(data, options) {
|
|
@@ -7,6 +7,61 @@ const logging = require('@tryghost/logging');
|
|
|
7
7
|
*/
|
|
8
8
|
module.exports = function (Bookshelf) {
|
|
9
9
|
Bookshelf.Model = Bookshelf.Model.extend({
|
|
10
|
+
/**
|
|
11
|
+
* Constructs data to be stored in the database with info
|
|
12
|
+
* on particular actions
|
|
13
|
+
*/
|
|
14
|
+
getAction(event, options) {
|
|
15
|
+
const actor = this.getActor(options);
|
|
16
|
+
|
|
17
|
+
// @NOTE: we ignore internal updates (`options.context.internal`) for now
|
|
18
|
+
if (!actor) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!this.actionsCollectCRUD) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let resourceType = this.actionsResourceType;
|
|
27
|
+
|
|
28
|
+
if (typeof resourceType === 'function') {
|
|
29
|
+
resourceType = resourceType.bind(this)();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!resourceType) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let context = {};
|
|
37
|
+
|
|
38
|
+
if (this.actionsExtraContext && Array.isArray(this.actionsExtraContext)) {
|
|
39
|
+
for (const c of this.actionsExtraContext) {
|
|
40
|
+
context[c] = this.get(c) || this.previous(c);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (event === 'deleted') {
|
|
45
|
+
context.primary_name = (this.previous('title') || this.previous('name'));
|
|
46
|
+
} else if (['added', 'edited'].includes(event)) {
|
|
47
|
+
context.primary_name = (this.get('title') || this.get('name') || this.previous('title') || this.previous('name'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const data = {
|
|
51
|
+
event,
|
|
52
|
+
resource_id: this.id || this.previous('id'),
|
|
53
|
+
resource_type: resourceType,
|
|
54
|
+
actor_id: actor.id,
|
|
55
|
+
actor_type: actor.type
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (context && Object.keys(context).length) {
|
|
59
|
+
data.context = context;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return data;
|
|
63
|
+
},
|
|
64
|
+
|
|
10
65
|
/**
|
|
11
66
|
* @NOTE:
|
|
12
67
|
*
|
|
@@ -6,6 +6,9 @@ const {NoPermissionError} = require('@tryghost/errors');
|
|
|
6
6
|
const Integration = ghostBookshelf.Model.extend({
|
|
7
7
|
tableName: 'integrations',
|
|
8
8
|
|
|
9
|
+
actionsCollectCRUD: true,
|
|
10
|
+
actionsResourceType: 'integration',
|
|
11
|
+
|
|
9
12
|
relationships: ['api_keys', 'webhooks'],
|
|
10
13
|
|
|
11
14
|
relationshipBelongsTo: {
|