ghost 5.8.2 → 5.9.1
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-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-custom-theme-settings-service-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-job-manager-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-members-api-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-cache-control-0.0.0.tgz +0 -0
- package/components/tryghost-mw-error-handler-0.0.0.tgz +0 -0
- package/components/tryghost-package-json-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/content/themes/casper/README.md +1 -1
- package/content/themes/casper/assets/built/portal.min.js +3 -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 +119 -37
- package/content/themes/casper/package.json +3 -3
- package/content/themes/casper/partials/post-card.hbs +3 -1
- package/content/themes/casper/post.hbs +7 -7
- package/content/themes/casper/yarn.lock +69 -68
- package/core/boot.js +2 -0
- package/core/built/admin/assets/{chunk.143.52be0261357eddd34833.js → chunk.143.9b9ae7a21f4620dc58b9.js} +5 -5
- package/core/built/admin/assets/{chunk.178.5e51541237cbe44b2a57.js → chunk.178.711d94cc8a84182a6c38.js} +4 -4
- package/core/built/admin/assets/{chunk.351.cbc224ca65c14ef5322d.js → chunk.351.73f27952f867334a8228.js} +3 -3
- package/core/built/admin/assets/{chunk.351.cbc224ca65c14ef5322d.js.LICENSE.txt → chunk.351.73f27952f867334a8228.js.LICENSE.txt} +0 -0
- package/core/built/admin/assets/{ghost-b469423d0fbe5e40af17b560f7e3cead.css → ghost-686c383caa6a3469cefb939ab10e21b6.css} +1 -1
- package/core/built/admin/assets/{ghost-dark-bcb6f4517a2dfe23a0a280632bfca00c.css → ghost-dark-6814c399ff5b3d9c8efe2d92bc7ec779.css} +1 -1
- package/core/built/admin/assets/{ghost-a66a04418efe85083a3adca0fb16bb52.js → ghost-eca1a709a74b1af277e48aad4e16c9db.js} +94 -92
- package/core/built/admin/assets/{vendor-46baf13852f545c6c89756c8e0ccbff2.js → vendor-516c9e43b4aeb92079dc1ab92c9ce492.js} +3 -3
- package/core/built/admin/index.html +6 -6
- package/core/frontend/helpers/comment_count.js +7 -15
- package/core/frontend/helpers/comments.js +4 -16
- package/core/frontend/helpers/ghost_head.js +1 -1
- package/core/frontend/public/ghost.min.css +1 -1
- package/core/frontend/src/comment-counts/js/comment-counts.js +12 -1
- package/core/server/api/endpoints/comments-members.js +23 -1
- package/core/server/api/endpoints/index.js +52 -52
- package/core/server/api/endpoints/utils/serializers/input/comments.js +18 -0
- package/core/server/api/endpoints/utils/serializers/input/db.js +1 -1
- package/core/server/api/endpoints/utils/serializers/input/index.js +4 -0
- package/core/server/api/endpoints/utils/serializers/input/integrations.js +2 -2
- package/core/server/api/endpoints/utils/serializers/input/tiers.js +1 -1
- package/core/server/api/{shared → endpoints/utils}/serializers/input/utils/settings-filter-type-group-mapper.js +0 -0
- package/core/server/api/{shared → endpoints/utils}/serializers/input/utils/settings-key-group-mapper.js +0 -0
- package/core/server/api/{shared → endpoints/utils}/serializers/input/utils/settings-key-type-mapper.js +0 -0
- package/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +12 -12
- package/core/server/api/endpoints/utils/serializers/output/tiers.js +1 -1
- package/core/server/api/index.js +0 -2
- package/core/server/data/db/connection.js +0 -4
- package/core/server/data/importer/importers/data/settings.js +2 -2
- package/core/server/data/migrations/versions/5.9/2022-08-09-08-32-added-new-integration-type.js +24 -0
- package/core/server/data/schema/clients/mysql.js +0 -15
- package/core/server/data/schema/commands.js +0 -9
- package/core/server/data/schema/fixtures/fixtures.json +1 -1
- package/core/server/data/schema/schema.js +3 -3
- package/core/server/models/base/plugins/user-type.js +1 -9
- package/core/server/models/comment.js +96 -15
- package/core/server/models/label.js +14 -0
- package/core/server/models/newsletter.js +21 -0
- package/core/server/models/stripe-customer-subscription.js +6 -0
- package/core/server/models/tag.js +20 -0
- package/core/server/models/user.js +20 -0
- package/core/server/run-update-check.js +1 -1
- package/core/server/services/auth/api-key/admin.js +1 -1
- package/core/server/services/auth/api-key/content.js +1 -1
- package/core/server/services/bulk-email/bulk-email-processor.js +18 -11
- package/core/server/services/bulk-email/index.js +1 -17
- package/core/server/services/comments/controller.js +9 -0
- package/core/server/services/comments/email-templates/new-comment-reply.hbs +2 -2
- package/core/server/services/comments/email-templates/new-comment.hbs +2 -2
- package/core/server/services/comments/email-templates/report.hbs +2 -2
- package/core/server/services/comments/service.js +16 -3
- package/core/server/services/comments/stats.js +2 -0
- package/core/server/services/email-analytics/jobs/fetch-latest.js +1 -1
- package/core/server/services/mega/post-email-serializer.js +2 -2
- package/core/server/services/permissions/can-this.js +154 -161
- package/core/server/services/permissions/parse-context.js +1 -8
- package/core/server/services/webhooks/serialize.js +3 -3
- package/core/server/web/api/endpoints/admin/routes.js +1 -1
- package/core/server/web/api/endpoints/content/routes.js +1 -1
- package/core/server/web/api/testmode/jobs/graceful-job.js +1 -1
- package/core/server/web/comments/routes.js +2 -1
- package/core/server/web/shared/middleware/index.js +1 -1
- package/core/shared/config/defaults.json +2 -2
- package/core/shared/labs.js +0 -1
- package/package.json +32 -29
- package/yarn.lock +330 -276
- package/core/server/api/README.md +0 -130
- package/core/server/api/shared/frame.js +0 -95
- package/core/server/api/shared/headers.js +0 -152
- package/core/server/api/shared/http.js +0 -127
- package/core/server/api/shared/index.js +0 -25
- package/core/server/api/shared/pipeline.js +0 -259
- package/core/server/api/shared/serializers/handle.js +0 -140
- package/core/server/api/shared/serializers/index.js +0 -13
- package/core/server/api/shared/serializers/input/all.js +0 -41
- package/core/server/api/shared/serializers/input/index.js +0 -5
- package/core/server/api/shared/serializers/output/index.js +0 -1
- package/core/server/api/shared/utils/index.js +0 -5
- package/core/server/api/shared/utils/options.js +0 -23
- package/core/server/api/shared/validators/handle.js +0 -68
- package/core/server/api/shared/validators/index.js +0 -9
- package/core/server/api/shared/validators/input/all.js +0 -213
- package/core/server/api/shared/validators/input/index.js +0 -5
- package/core/server/services/bulk-email/mailgun.js +0 -122
- package/core/server/web/shared/middleware/cache-control.js +0 -43
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
border-bottom-color: #EEF5F8;
|
|
95
95
|
}
|
|
96
96
|
a {
|
|
97
|
-
color: #
|
|
97
|
+
color: #15212A;
|
|
98
98
|
}
|
|
99
99
|
</style>
|
|
100
100
|
</head>
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
<tr>
|
|
117
117
|
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
|
|
118
118
|
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 20px; color: #15212A; font-weight: bold; line-height: 25px; margin: 0; margin-bottom: 15px;">Hey there,</p>
|
|
119
|
-
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">{{reporter}} has reported the comment below on <a href="{{postUrl}}" target="_blank">{{postTitle}}</a>. This comment will remain visible until you choose to remove it, which can be done directly on the post.</p>
|
|
119
|
+
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">{{reporter}} has reported the comment below on <a href="{{postUrl}}" target="_blank" style="font-weight: bold; text-decoration: underline; color: #15212A;">{{postTitle}}</a>. This comment will remain visible until you choose to remove it, which can be done directly on the post.</p>
|
|
120
120
|
|
|
121
121
|
<table width="100" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; table-layout: fixed; width: 100%; min-width: 100%; box-sizing: border-box; background: #F9F9FA; border-radius: 7px;">
|
|
122
122
|
<tbody>
|
|
@@ -115,7 +115,18 @@ class CommentsService {
|
|
|
115
115
|
*/
|
|
116
116
|
async getComments(options) {
|
|
117
117
|
this.checkEnabled();
|
|
118
|
-
const page = await this.models.Comment.findPage(options);
|
|
118
|
+
const page = await this.models.Comment.findPage({...options, parentId: null});
|
|
119
|
+
|
|
120
|
+
return page;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param {string} id - The ID of the Comment to get replies from
|
|
125
|
+
* @param {any} options
|
|
126
|
+
*/
|
|
127
|
+
async getReplies(id, options) {
|
|
128
|
+
this.checkEnabled();
|
|
129
|
+
const page = await this.models.Comment.findPage({...options, parentId: id});
|
|
119
130
|
|
|
120
131
|
return page;
|
|
121
132
|
}
|
|
@@ -181,7 +192,8 @@ class CommentsService {
|
|
|
181
192
|
commentId: model.id
|
|
182
193
|
}));
|
|
183
194
|
|
|
184
|
-
|
|
195
|
+
// Instead of returning the model, fetch it again, so we have all the relations properly fetched
|
|
196
|
+
return await this.models.Comment.findOne({id: model.id}, {...options, require: true});
|
|
185
197
|
}
|
|
186
198
|
|
|
187
199
|
/**
|
|
@@ -240,7 +252,8 @@ class CommentsService {
|
|
|
240
252
|
commentId: model.id
|
|
241
253
|
}));
|
|
242
254
|
|
|
243
|
-
|
|
255
|
+
// Instead of returning the model, fetch it again, so we have all the relations properly fetched
|
|
256
|
+
return await this.models.Comment.findOne({id: model.id}, {...options, require: true});
|
|
244
257
|
}
|
|
245
258
|
|
|
246
259
|
/**
|
|
@@ -11,6 +11,7 @@ module.exports = class CommentsStatsService {
|
|
|
11
11
|
const results = await this.db.knex('comments')
|
|
12
12
|
.select(this.db.knex.raw(`COUNT(*) AS count, post_id`))
|
|
13
13
|
.groupBy('post_id')
|
|
14
|
+
.where('status', 'published')
|
|
14
15
|
.whereIn('post_id', ids);
|
|
15
16
|
|
|
16
17
|
const counts = ids.reduce((memo, id) => {
|
|
@@ -30,6 +31,7 @@ module.exports = class CommentsStatsService {
|
|
|
30
31
|
async getAllCounts() {
|
|
31
32
|
const results = await this.db.knex('comments')
|
|
32
33
|
.select(this.db.knex.raw(`COUNT(*) AS count, post_id`))
|
|
34
|
+
.where('status', 'published')
|
|
33
35
|
.groupBy('post_id');
|
|
34
36
|
|
|
35
37
|
/** @type Object<string, number> */
|
|
@@ -5,7 +5,7 @@ const urlUtils = require('../../../shared/url-utils');
|
|
|
5
5
|
const labs = require('../../../shared/labs');
|
|
6
6
|
const moment = require('moment-timezone');
|
|
7
7
|
const api = require('../../api').endpoints;
|
|
8
|
-
const
|
|
8
|
+
const apiFramework = require('@tryghost/api-framework');
|
|
9
9
|
const {URL} = require('url');
|
|
10
10
|
const mobiledocLib = require('../../lib/mobiledoc');
|
|
11
11
|
const htmlToPlaintext = require('@tryghost/html-to-plaintext');
|
|
@@ -104,7 +104,7 @@ const serializePostModel = async (model) => {
|
|
|
104
104
|
const frame = {options: {context: {user: true}, formats: 'mobiledoc'}};
|
|
105
105
|
const docName = 'posts';
|
|
106
106
|
|
|
107
|
-
await
|
|
107
|
+
await apiFramework
|
|
108
108
|
.serializers
|
|
109
109
|
.handle
|
|
110
110
|
.output(model, {docName: docName, method: 'read'}, api.serializers.output, frame);
|
|
@@ -12,186 +12,179 @@ const messages = {
|
|
|
12
12
|
noActionsMapFoundError: 'No actions map found, ensure you have loaded permissions into database and then call permissions.init() before use.'
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
permission: models.Permission,
|
|
28
|
-
setting: models.Settings,
|
|
29
|
-
invite: models.Invite,
|
|
30
|
-
integration: models.Integration,
|
|
31
|
-
comment: models.Comment
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// Iterate through the object types, i.e. ['post', 'tag', 'user']
|
|
35
|
-
return _.reduce(objTypes, function (objTypeHandlers, objType) {
|
|
36
|
-
// Grab the TargetModel through the objectTypeModelMap
|
|
37
|
-
const TargetModel = objectTypeModelMap[objType];
|
|
38
|
-
|
|
39
|
-
// Create the 'handler' for the object type;
|
|
40
|
-
// the '.post()' in canThis(user).edit.post()
|
|
41
|
-
objTypeHandlers[objType] = function (modelOrId, unsafeAttrs) {
|
|
42
|
-
let modelId;
|
|
43
|
-
unsafeAttrs = unsafeAttrs || {};
|
|
44
|
-
|
|
45
|
-
// If it's an internal request, resolve immediately
|
|
46
|
-
if (context.internal) {
|
|
47
|
-
return Promise.resolve();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (_.isNumber(modelOrId) || _.isString(modelOrId)) {
|
|
51
|
-
// It's an id already, do nothing
|
|
52
|
-
modelId = modelOrId;
|
|
53
|
-
} else if (modelOrId) {
|
|
54
|
-
// It's a model, get the id
|
|
55
|
-
modelId = modelOrId.id;
|
|
56
|
-
}
|
|
57
|
-
// Wait for the user loading to finish
|
|
58
|
-
return permissionLoad.then(function (loadedPermissions) {
|
|
59
|
-
// Iterate through the user permissions looking for an affirmation
|
|
60
|
-
const userPermissions = loadedPermissions.user ? loadedPermissions.user.permissions : null;
|
|
61
|
-
const apiKeyPermissions = loadedPermissions.apiKey ? loadedPermissions.apiKey.permissions : null;
|
|
62
|
-
const memberPermissions = loadedPermissions.member ? loadedPermissions.member.permissions : null;
|
|
63
|
-
|
|
64
|
-
let hasUserPermission;
|
|
65
|
-
let hasApiKeyPermission;
|
|
66
|
-
let hasMemberPermission = false;
|
|
67
|
-
|
|
68
|
-
const checkPermission = function (perm) {
|
|
69
|
-
let permObjId;
|
|
70
|
-
|
|
71
|
-
// Look for a matching action type and object type first
|
|
72
|
-
if (perm.get('action_type') !== actType || perm.get('object_type') !== objType) {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Grab the object id (if specified, could be null)
|
|
77
|
-
permObjId = perm.get('object_id');
|
|
78
|
-
|
|
79
|
-
// If we didn't specify a model (any thing)
|
|
80
|
-
// or the permission didn't have an id scope set
|
|
81
|
-
// then the "thing" has permission
|
|
82
|
-
if (!modelId || !permObjId) {
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Otherwise, check if the id's match
|
|
87
|
-
// TODO: String vs Int comparison possibility here?
|
|
88
|
-
return modelId === permObjId;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
if (loadedPermissions.user && _.some(loadedPermissions.user.roles, {name: 'Owner'})) {
|
|
92
|
-
hasUserPermission = true;
|
|
93
|
-
} else if (!_.isEmpty(userPermissions)) {
|
|
94
|
-
hasUserPermission = _.some(userPermissions, checkPermission);
|
|
95
|
-
}
|
|
15
|
+
class CanThisResult {
|
|
16
|
+
buildObjectTypeHandlers(objTypes, actType, context, permissionLoad) {
|
|
17
|
+
const objectTypeModelMap = {
|
|
18
|
+
post: models.Post,
|
|
19
|
+
role: models.Role,
|
|
20
|
+
user: models.User,
|
|
21
|
+
permission: models.Permission,
|
|
22
|
+
setting: models.Settings,
|
|
23
|
+
invite: models.Invite,
|
|
24
|
+
integration: models.Integration,
|
|
25
|
+
comment: models.Comment
|
|
26
|
+
};
|
|
96
27
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
28
|
+
// Iterate through the object types, i.e. ['post', 'tag', 'user']
|
|
29
|
+
return _.reduce(objTypes, function (objTypeHandlers, objType) {
|
|
30
|
+
// Grab the TargetModel through the objectTypeModelMap
|
|
31
|
+
const TargetModel = objectTypeModelMap[objType];
|
|
100
32
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
hasApiKeyPermission = _.some(apiKeyPermissions, checkPermission);
|
|
107
|
-
}
|
|
33
|
+
// Create the 'handler' for the object type;
|
|
34
|
+
// the '.post()' in canThis(user).edit.post()
|
|
35
|
+
objTypeHandlers[objType] = function (modelOrId, unsafeAttrs) {
|
|
36
|
+
let modelId;
|
|
37
|
+
unsafeAttrs = unsafeAttrs || {};
|
|
108
38
|
|
|
109
|
-
//
|
|
110
|
-
if (
|
|
111
|
-
return
|
|
112
|
-
modelId, actType, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasApiKeyPermission, hasMemberPermission
|
|
113
|
-
);
|
|
39
|
+
// If it's an internal request, resolve immediately
|
|
40
|
+
if (context.internal) {
|
|
41
|
+
return Promise.resolve();
|
|
114
42
|
}
|
|
115
43
|
|
|
116
|
-
if (
|
|
117
|
-
|
|
44
|
+
if (_.isNumber(modelOrId) || _.isString(modelOrId)) {
|
|
45
|
+
// It's an id already, do nothing
|
|
46
|
+
modelId = modelOrId;
|
|
47
|
+
} else if (modelOrId) {
|
|
48
|
+
// It's a model, get the id
|
|
49
|
+
modelId = modelOrId.id;
|
|
118
50
|
}
|
|
51
|
+
// Wait for the user loading to finish
|
|
52
|
+
return permissionLoad.then(function (loadedPermissions) {
|
|
53
|
+
// Iterate through the user permissions looking for an affirmation
|
|
54
|
+
const userPermissions = loadedPermissions.user ? loadedPermissions.user.permissions : null;
|
|
55
|
+
const apiKeyPermissions = loadedPermissions.apiKey ? loadedPermissions.apiKey.permissions : null;
|
|
56
|
+
const memberPermissions = loadedPermissions.member ? loadedPermissions.member.permissions : null;
|
|
57
|
+
|
|
58
|
+
let hasUserPermission;
|
|
59
|
+
let hasApiKeyPermission;
|
|
60
|
+
let hasMemberPermission = false;
|
|
61
|
+
|
|
62
|
+
const checkPermission = function (perm) {
|
|
63
|
+
let permObjId;
|
|
64
|
+
|
|
65
|
+
// Look for a matching action type and object type first
|
|
66
|
+
if (perm.get('action_type') !== actType || perm.get('object_type') !== objType) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Grab the object id (if specified, could be null)
|
|
71
|
+
permObjId = perm.get('object_id');
|
|
72
|
+
|
|
73
|
+
// If we didn't specify a model (any thing)
|
|
74
|
+
// or the permission didn't have an id scope set
|
|
75
|
+
// then the "thing" has permission
|
|
76
|
+
if (!modelId || !permObjId) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Otherwise, check if the id's match
|
|
81
|
+
// TODO: String vs Int comparison possibility here?
|
|
82
|
+
return modelId === permObjId;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (loadedPermissions.user && _.some(loadedPermissions.user.roles, {name: 'Owner'})) {
|
|
86
|
+
hasUserPermission = true;
|
|
87
|
+
} else if (!_.isEmpty(userPermissions)) {
|
|
88
|
+
hasUserPermission = _.some(userPermissions, checkPermission);
|
|
89
|
+
}
|
|
119
90
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return objTypeHandlers;
|
|
125
|
-
}, {});
|
|
126
|
-
};
|
|
91
|
+
if (loadedPermissions.member) {
|
|
92
|
+
hasMemberPermission = _.some(memberPermissions, checkPermission);
|
|
93
|
+
}
|
|
127
94
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
95
|
+
// Check api key permissions if they were passed
|
|
96
|
+
hasApiKeyPermission = true;
|
|
97
|
+
if (!_.isNull(apiKeyPermissions)) {
|
|
98
|
+
// api key request have no user, but we want the user permissions checks to pass
|
|
99
|
+
hasUserPermission = true;
|
|
100
|
+
hasApiKeyPermission = _.some(apiKeyPermissions, checkPermission);
|
|
101
|
+
}
|
|
134
102
|
|
|
135
|
-
|
|
136
|
-
|
|
103
|
+
// Offer a chance for the TargetModel to override the results
|
|
104
|
+
if (TargetModel && _.isFunction(TargetModel.permissible)) {
|
|
105
|
+
return TargetModel.permissible(
|
|
106
|
+
modelId, actType, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasApiKeyPermission, hasMemberPermission
|
|
107
|
+
);
|
|
108
|
+
}
|
|
137
109
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
110
|
+
if (hasUserPermission && hasApiKeyPermission) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
141
113
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
} else {
|
|
146
|
-
// Resolve null if no context.user to prevent db call
|
|
147
|
-
userPermissionLoad = Promise.resolve(null);
|
|
148
|
-
}
|
|
114
|
+
return Promise.reject(new errors.NoPermissionError({message: tpl(messages.noPermissionToAction)}));
|
|
115
|
+
});
|
|
116
|
+
};
|
|
149
117
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
apiKeyPermissionLoad = providers.apiKey(context.api_key.id);
|
|
153
|
-
} else {
|
|
154
|
-
// Resolve null if no context.api_key
|
|
155
|
-
apiKeyPermissionLoad = Promise.resolve(null);
|
|
118
|
+
return objTypeHandlers;
|
|
119
|
+
}, {});
|
|
156
120
|
}
|
|
157
121
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
122
|
+
beginCheck(context) {
|
|
123
|
+
const self = this;
|
|
124
|
+
let userPermissionLoad;
|
|
125
|
+
let apiKeyPermissionLoad;
|
|
126
|
+
let memberPermissionLoad;
|
|
127
|
+
let permissionsLoad;
|
|
128
|
+
|
|
129
|
+
// Get context.user, context.api_key and context.app
|
|
130
|
+
context = parseContext(context);
|
|
131
|
+
|
|
132
|
+
if (actionsMap.empty()) {
|
|
133
|
+
throw new errors.InternalServerError({message: tpl(messages.noActionsMapFoundError)});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Kick off loading of user permissions if necessary
|
|
137
|
+
if (context.user) {
|
|
138
|
+
userPermissionLoad = providers.user(context.user);
|
|
139
|
+
} else {
|
|
140
|
+
// Resolve null if no context.user to prevent db call
|
|
141
|
+
userPermissionLoad = Promise.resolve(null);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Kick off loading of api key permissions if necessary
|
|
145
|
+
if (context.api_key) {
|
|
146
|
+
apiKeyPermissionLoad = providers.apiKey(context.api_key.id);
|
|
147
|
+
} else {
|
|
148
|
+
// Resolve null if no context.api_key
|
|
149
|
+
apiKeyPermissionLoad = Promise.resolve(null);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (context.member) {
|
|
153
|
+
memberPermissionLoad = providers.member(context.member.id);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Wait for both user and app permissions to load
|
|
157
|
+
permissionsLoad = Promise.all([userPermissionLoad, apiKeyPermissionLoad, memberPermissionLoad]).then(function (result) {
|
|
158
|
+
return {
|
|
159
|
+
user: result[0],
|
|
160
|
+
apiKey: result[1],
|
|
161
|
+
member: result[2]
|
|
162
|
+
};
|
|
163
|
+
});
|
|
161
164
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
user
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
// Define a property for the action on the result;
|
|
178
|
-
// the '.edit' in canThis(user).edit.post()
|
|
179
|
-
Object.defineProperty(self, actType, {
|
|
180
|
-
writable: false,
|
|
181
|
-
enumerable: false,
|
|
182
|
-
configurable: false,
|
|
183
|
-
value: objTypeHandlers
|
|
165
|
+
// Iterate through the actions and their related object types
|
|
166
|
+
_.each(actionsMap.getAll(), function (objTypes, actType) {
|
|
167
|
+
// Build up the object type handlers;
|
|
168
|
+
// the '.post()' parts in canThis(user).edit.post()
|
|
169
|
+
const objTypeHandlers = self.buildObjectTypeHandlers(objTypes, actType, context, permissionsLoad);
|
|
170
|
+
|
|
171
|
+
// Define a property for the action on the result;
|
|
172
|
+
// the '.edit' in canThis(user).edit.post()
|
|
173
|
+
Object.defineProperty(self, actType, {
|
|
174
|
+
writable: false,
|
|
175
|
+
enumerable: false,
|
|
176
|
+
configurable: false,
|
|
177
|
+
value: objTypeHandlers
|
|
178
|
+
});
|
|
184
179
|
});
|
|
185
|
-
});
|
|
186
180
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
181
|
+
// Return this for chaining
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
190
185
|
|
|
191
|
-
|
|
186
|
+
module.exports = function canThis(context) {
|
|
192
187
|
const result = new CanThisResult();
|
|
193
188
|
|
|
194
189
|
return result.beginCheck(context);
|
|
195
190
|
};
|
|
196
|
-
|
|
197
|
-
module.exports = canThis;
|
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Utility function, to expand strings out into objects.
|
|
5
5
|
* @param {Object|String} context
|
|
6
|
-
* @return {{internal: boolean,
|
|
6
|
+
* @return {{internal: boolean, user: integer|null, public: boolean, api_key: Object|null}}
|
|
7
7
|
*/
|
|
8
8
|
module.exports = function parseContext(context) {
|
|
9
9
|
const parsed = {
|
|
10
10
|
internal: false,
|
|
11
|
-
external: false,
|
|
12
11
|
user: null,
|
|
13
12
|
api_key: null,
|
|
14
13
|
integration: null,
|
|
@@ -16,12 +15,6 @@ module.exports = function parseContext(context) {
|
|
|
16
15
|
public: true
|
|
17
16
|
};
|
|
18
17
|
|
|
19
|
-
// NOTE: We use the `external` context for subscribers only at the moment.
|
|
20
|
-
if (context && (context === 'external' || context.external)) {
|
|
21
|
-
parsed.external = true;
|
|
22
|
-
parsed.public = false;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
18
|
if (context && (context === 'internal' || context.internal)) {
|
|
26
19
|
parsed.internal = true;
|
|
27
20
|
parsed.public = false;
|
|
@@ -2,7 +2,7 @@ module.exports = (event, model) => {
|
|
|
2
2
|
const _ = require('lodash');
|
|
3
3
|
const {sequence} = require('@tryghost/promise');
|
|
4
4
|
const api = require('../../api').endpoints;
|
|
5
|
-
const
|
|
5
|
+
const apiFramework = require('@tryghost/api-framework');
|
|
6
6
|
|
|
7
7
|
const resourceName = event.match(/(\w+)\./)[1];
|
|
8
8
|
const docName = `${resourceName}s`;
|
|
@@ -23,7 +23,7 @@ module.exports = (event, model) => {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
return
|
|
26
|
+
return apiFramework
|
|
27
27
|
.serializers
|
|
28
28
|
.handle
|
|
29
29
|
.output(model, {docName: docName, method: 'read'}, api.serializers.output, frame)
|
|
@@ -46,7 +46,7 @@ module.exports = (event, model) => {
|
|
|
46
46
|
frame.options.withRelated = ['tags', 'authors'];
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
return
|
|
49
|
+
return apiFramework
|
|
50
50
|
.serializers
|
|
51
51
|
.handle
|
|
52
52
|
.output(model, {docName: docName, method: 'read'}, api.serializers.output, frame)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const express = require('../../../../../shared/express');
|
|
2
2
|
const api = require('../../../../api').endpoints;
|
|
3
|
-
const http = require('
|
|
3
|
+
const {http} = require('@tryghost/api-framework');
|
|
4
4
|
const apiMw = require('../../middleware');
|
|
5
5
|
const mw = require('./middleware');
|
|
6
6
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const express = require('../../../../../shared/express');
|
|
2
2
|
const cors = require('cors');
|
|
3
3
|
const api = require('../../../../api').endpoints;
|
|
4
|
-
const http = require('
|
|
4
|
+
const {http} = require('@tryghost/api-framework');
|
|
5
5
|
const mw = require('./middleware');
|
|
6
6
|
const config = require('../../../../../shared/config');
|
|
7
7
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const express = require('../../../shared/express');
|
|
2
2
|
const api = require('../../api').endpoints;
|
|
3
|
-
const http = require('
|
|
3
|
+
const {http} = require('@tryghost/api-framework');
|
|
4
4
|
|
|
5
5
|
const bodyParser = require('body-parser');
|
|
6
6
|
const membersService = require('../../../server/services/members');
|
|
@@ -23,6 +23,7 @@ module.exports = function apiRoutes() {
|
|
|
23
23
|
|
|
24
24
|
router.post('/:id/like', http(api.commentsMembers.like));
|
|
25
25
|
router.delete('/:id/like', http(api.commentsMembers.unlike));
|
|
26
|
+
router.get('/:id/replies', http(api.commentsMembers.replies));
|
|
26
27
|
|
|
27
28
|
router.post('/:id/report', http(api.commentsMembers.report));
|
|
28
29
|
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
},
|
|
138
138
|
"portal": {
|
|
139
139
|
"url": "https://cdn.jsdelivr.net/npm/@tryghost/portal@~{version}/umd/portal.min.js",
|
|
140
|
-
"version": "2.
|
|
140
|
+
"version": "2.7"
|
|
141
141
|
},
|
|
142
142
|
"sodoSearch": {
|
|
143
143
|
"url": "https://cdn.jsdelivr.net/npm/@tryghost/sodo-search@~{version}/umd/sodo-search.min.js",
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
"comments": {
|
|
148
148
|
"url": "https://cdn.jsdelivr.net/npm/@tryghost/comments-ui@~{version}/umd/comments-ui.min.js",
|
|
149
149
|
"styles": "https://cdn.jsdelivr.net/npm/@tryghost/comments-ui@~{version}/umd/main.css",
|
|
150
|
-
"version": "0.
|
|
150
|
+
"version": "0.8"
|
|
151
151
|
},
|
|
152
152
|
"editor": {
|
|
153
153
|
"url": "https://unpkg.com/@tryghost/koenig-react/dist/umd/koenig-react.min.js"
|