strapi-plugin-comments 2.0.0-rc.1 → 2.0.0-rc.2
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/README.md +71 -60
- package/admin/src/components/DiscussionThread/index.js +1 -1
- package/admin/src/components/DiscussionThreadItem/index.js +2 -4
- package/content-types/comment.js +0 -4
- package/package.json +4 -3
- package/server/controllers/client.js +7 -2
- package/server/controllers/utils/parsers.js +3 -1
- package/server/graphql/mutations/createAbuseReport.js +8 -3
- package/server/graphql/mutations/createComment.js +8 -3
- package/server/graphql/mutations/removeComment.js +8 -3
- package/server/graphql/mutations/updateComment.js +8 -3
- package/server/graphql/resolvers-config.js +13 -8
- package/server/graphql/types/comment-nested.js +0 -1
- package/server/graphql/types/comment.js +0 -1
- package/server/graphql/types/create-comment-author.js +1 -1
- package/server/graphql/types/identify-comment-author.js +1 -1
- package/server/graphql/types/index.js +0 -1
- package/server/services/admin.js +10 -7
- package/server/services/client.js +43 -28
- package/server/services/common.js +19 -14
- package/server/services/utils/functions.js +23 -18
- package/server/graphql/types/comment-author-user.js +0 -9
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
<div align="center">
|
|
1
|
+
<div align="center" width="150px">
|
|
2
2
|
<img style="width: 150px; height: auto;" src="public/assets/logo.png" alt="Logo - Strapi Comments plugin" />
|
|
3
|
+
</div>
|
|
4
|
+
<div align="center">
|
|
3
5
|
<h1>Strapi v4 - Comments plugin</h1>
|
|
4
6
|
<p>Powerfull Strapi based comments moderation tool for you and your users</p>
|
|
5
7
|
<a href="https://www.npmjs.org/package/strapi-plugin-comments">
|
|
6
|
-
<img src="https://img.shields.io/
|
|
8
|
+
<img alt="GitHub package.json version" src="https://img.shields.io/github/package-json/v/VirtusLab-Open-Source/strapi-plugin-comments?label=npm&logo=npm">
|
|
7
9
|
</a>
|
|
8
10
|
<a href="https://www.npmjs.org/package/strapi-plugin-comments">
|
|
9
11
|
<img src="https://img.shields.io/npm/dm/strapi-plugin-comments.svg" alt="Monthly download on NPM" />
|
|
@@ -76,7 +78,7 @@ In our minimum support we're following [official Node.js releases timelines](htt
|
|
|
76
78
|
|
|
77
79
|
**Supported Strapi versions**:
|
|
78
80
|
|
|
79
|
-
- Strapi v4.0.
|
|
81
|
+
- Strapi v4.0.6 (recently tested)
|
|
80
82
|
- Strapi v4.x
|
|
81
83
|
|
|
82
84
|
> This plugin is designed for **Strapi v4** and is not working with v3.x. To get version for **Strapi v3** install version [v1.x](https://github.com/VirtusLab-Open-Source/strapi-plugin-comments/tree/strapi-v3).
|
|
@@ -101,6 +103,9 @@ To setup amend default plugin configuration we recommend to put following snippe
|
|
|
101
103
|
reportReasons: {
|
|
102
104
|
'MY_CUSTOM_REASON': 'MY_CUSTOM_REASON',
|
|
103
105
|
},
|
|
106
|
+
gql: {
|
|
107
|
+
// ...
|
|
108
|
+
},
|
|
104
109
|
},
|
|
105
110
|
//...
|
|
106
111
|
});
|
|
@@ -112,12 +117,36 @@ To setup amend default plugin configuration we recommend to put following snippe
|
|
|
112
117
|
- `approvalFlow` - list of Content Types which are supporting approval flow. Values must be in format like `'api::<collection name>.<content type name>'`. For not included, posted comments are going to be immediately visible.
|
|
113
118
|
- `entryLabel` - ordered list of property names per Content Type to generate related entity label. Keys must be in format like `'api::<collection name>.<content type name>'`. Default formatting set as `*`.
|
|
114
119
|
- `reportReasons` - set of enums you would like to use for issuing abuse reports. Provided by default `'BAD_LANGUAGE'`, `'DISCRIMINATION'` and `'OTHER'`.
|
|
120
|
+
- `gql` - specific configuration for GraphQL. See [Additional GQL Configuration](#additional-gql-configuration)
|
|
115
121
|
|
|
116
122
|
## Additional GQL Configuration
|
|
117
|
-
All you need to do is to install and enable `@strapi/plugin-graphql` for you instance based on the **[official Strapi v4 docs](https://docs.strapi.io/developer-docs/latest/plugins/graphql.html#configurations)
|
|
123
|
+
All you need to do is to install and enable `@strapi/plugin-graphql` for you instance based on the **[official Strapi v4 docs](https://docs.strapi.io/developer-docs/latest/plugins/graphql.html#configurations)** and decide if you would like to call it by anyone (open for world) or only by authenticated users (Strapi users).
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
// ...
|
|
128
|
+
gql: {
|
|
129
|
+
auth: true, // Default: false
|
|
130
|
+
},
|
|
131
|
+
// ...
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Properties
|
|
136
|
+
- `auth` - does GraphQL Queries should be authenticated or not? Default: `false`
|
|
137
|
+
|
|
138
|
+
### Queries
|
|
118
139
|
|
|
119
140
|
See [available GQL specification section](#public-graphql-specification).
|
|
120
141
|
|
|
142
|
+
> If `auth` is set to `true` you must provide relevant authentication headers to all requests like for example:
|
|
143
|
+
>
|
|
144
|
+
> ```json
|
|
145
|
+
> {
|
|
146
|
+
> "Authorization": "Bearer <your token here>"
|
|
147
|
+
> }
|
|
148
|
+
> ```
|
|
149
|
+
|
|
121
150
|
## 👤 RBAC
|
|
122
151
|
Plugin provides granular permissions based on Strapi RBAC functionality.
|
|
123
152
|
|
|
@@ -134,7 +163,6 @@ Feature / Capability focused permissions:
|
|
|
134
163
|
|
|
135
164
|
## Base Comment model
|
|
136
165
|
|
|
137
|
-
### Generic (non Strapi User)
|
|
138
166
|
```json
|
|
139
167
|
{
|
|
140
168
|
"id": 1,
|
|
@@ -146,8 +174,8 @@ Feature / Capability focused permissions:
|
|
|
146
174
|
"removed": null,
|
|
147
175
|
"approvalStatus": "APPROVED", // Only in case of enabled approval flow. Default: null
|
|
148
176
|
"author": {
|
|
149
|
-
"id": "207ccfdc-94ba-45eb-979c-790f6f49c392",
|
|
150
|
-
"name": "Joe Doe",
|
|
177
|
+
"id": "207ccfdc-94ba-45eb-979c-790f6f49c392", // For Strapi users id reflects to the format used by your Strapi
|
|
178
|
+
"name": "Joe Doe", // For Strapi users it is equal to 'username' field
|
|
151
179
|
"email": "jdoe@sample.com",
|
|
152
180
|
"avatar": null,
|
|
153
181
|
},
|
|
@@ -156,47 +184,18 @@ Feature / Capability focused permissions:
|
|
|
156
184
|
"related": {}, // Related content type entity
|
|
157
185
|
"reports": [] // Reports issued against this comment
|
|
158
186
|
}
|
|
159
|
-
```
|
|
160
|
-
### Strapi User
|
|
161
|
-
```json
|
|
162
|
-
{
|
|
163
|
-
"id": 1,
|
|
164
|
-
"content": "My comment content",
|
|
165
|
-
"blocked": true,
|
|
166
|
-
"blockedThread": null,
|
|
167
|
-
"blockReason": null,
|
|
168
|
-
"removed": null,
|
|
169
|
-
"approvalStatus": "REJECTED", // Only in case of enabled approval flow. Default: null
|
|
170
|
-
"authorUser": {
|
|
171
|
-
"id": 1,
|
|
172
|
-
"username": "Sample User",
|
|
173
|
-
"email": "user@sample.com",
|
|
174
|
-
"provider": "local",
|
|
175
|
-
"confirmed": true,
|
|
176
|
-
"blocked": false,
|
|
177
|
-
"role": 1,
|
|
178
|
-
"created_at": "2020-07-10T08:38:03.157Z",
|
|
179
|
-
"updated_at": "2020-07-10T08:38:03.170Z"
|
|
180
|
-
},
|
|
181
|
-
"authorId": null,
|
|
182
|
-
"authorName": null,
|
|
183
|
-
"authorEmail": null,
|
|
184
|
-
"authorAvatar": null,
|
|
185
|
-
"createdAt": "2020-07-14T20:13:01.649Z",
|
|
186
|
-
"updatedAt": "2020-07-14T20:13:01.670Z",
|
|
187
|
-
"related": {}, // Related content type entity
|
|
188
|
-
"reports": [] // Reports issued against this comment
|
|
189
|
-
}
|
|
190
|
-
|
|
191
187
|
```
|
|
192
188
|
|
|
193
189
|
## 🕸️ Public REST API specification
|
|
194
190
|
|
|
191
|
+
**Strapi Users vs. Generic authors**
|
|
192
|
+
> Keep in mind that if you're using auth / authz your requests to setup proper user contexts it has got higher priority in order to take author data comparing to `author` property provided as part of your payload.
|
|
193
|
+
|
|
195
194
|
### Get Comments
|
|
196
195
|
|
|
197
196
|
*GraphQL equivalent: [Public GraphQL API -> Get Comments](#get-comments-1)*
|
|
198
197
|
|
|
199
|
-
`GET <host>/comments/api::<collection name>.<content type name>:<entity id>`
|
|
198
|
+
`GET <host>/api/comments/api::<collection name>.<content type name>:<entity id>`
|
|
200
199
|
|
|
201
200
|
Return a hierarchical tree structure of comments for specified instance of Content Type like for example `Page` with `ID: 1`
|
|
202
201
|
|
|
@@ -226,7 +225,7 @@ Return a hierarchical tree structure of comments for specified instance of Conte
|
|
|
226
225
|
|
|
227
226
|
*GraphQL equivalent: [Public GraphQL API -> Get Comments (flat structure)](#get-comments-flat-structure-1)*
|
|
228
227
|
|
|
229
|
-
`GET <host>/comments/api::<collection name>.<content type name>:<entity id>/flat`
|
|
228
|
+
`GET <host>/api/comments/api::<collection name>.<content type name>:<entity id>/flat`
|
|
230
229
|
|
|
231
230
|
Return a flat structure of comments for specified instance of Content Type like for example `Page` with `ID: 1`
|
|
232
231
|
|
|
@@ -254,7 +253,7 @@ Return a flat structure of comments for specified instance of Content Type like
|
|
|
254
253
|
|
|
255
254
|
*GraphQL equivalent: [Public GraphQL API -> Post a Comments](#post-a-comment-1)*
|
|
256
255
|
|
|
257
|
-
`POST <host>/comments/api::<collection name>.<content type name>:<entity id>`
|
|
256
|
+
`POST <host>/api/comments/api::<collection name>.<content type name>:<entity id>`
|
|
258
257
|
|
|
259
258
|
Posts a Comment related to specified instance of Content Type like for example `Page` with `ID: 1`
|
|
260
259
|
|
|
@@ -265,21 +264,23 @@ Posts a Comment related to specified instance of Content Type like for example `
|
|
|
265
264
|
*Generic (non Strapi User)*
|
|
266
265
|
```json
|
|
267
266
|
{
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
267
|
+
"author": {
|
|
268
|
+
"id": "<any ID like value>",
|
|
269
|
+
"name": "Joe Doe",
|
|
270
|
+
"email": "jdoe@sample.com",
|
|
271
|
+
"avatar": "<any image url>"
|
|
272
|
+
},
|
|
274
273
|
"content": "My sample response",
|
|
275
274
|
"threadOf": 2, // id of comment we would like to start / continue the thread (Optional)
|
|
276
275
|
}
|
|
277
276
|
```
|
|
278
277
|
|
|
279
278
|
*Strapi user*
|
|
279
|
+
|
|
280
|
+
> Author is taken directly from the request context
|
|
281
|
+
|
|
280
282
|
```json
|
|
281
283
|
{
|
|
282
|
-
"authorUser": 1, // id of a author user. Optional in case of 'enableUsers: true' in the plugin configuration
|
|
283
284
|
"content": "My sample response",
|
|
284
285
|
"threadOf": 2, // id of comment we would like to start / continue the thread (Optional)
|
|
285
286
|
}
|
|
@@ -301,7 +302,7 @@ Posts a Comment related to specified instance of Content Type like for example `
|
|
|
301
302
|
|
|
302
303
|
*GraphQL equivalent: [Public GraphQL API -> Update Comments](#update-comment-1)*
|
|
303
304
|
|
|
304
|
-
`PUT <host>/comments/api::<collection name>.<content type name>:<entity id>/comment/<commentId>`
|
|
305
|
+
`PUT <host>/api/comments/api::<collection name>.<content type name>:<entity id>/comment/<commentId>`
|
|
305
306
|
|
|
306
307
|
Updates a specified Comment content based on it `commentId` and related to specified instance of Content Type like for example `Page` with `ID: 1`
|
|
307
308
|
|
|
@@ -312,17 +313,19 @@ Updates a specified Comment content based on it `commentId` and related to speci
|
|
|
312
313
|
*Generic (non Strapi User)*
|
|
313
314
|
```json
|
|
314
315
|
{
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
"author": {
|
|
317
|
+
"id": "<any ID like value>"
|
|
318
|
+
},
|
|
318
319
|
"content": "My sample response"
|
|
319
320
|
}
|
|
320
321
|
```
|
|
321
322
|
|
|
322
323
|
*Strapi user*
|
|
324
|
+
|
|
325
|
+
> Author is taken directly from the request context
|
|
326
|
+
|
|
323
327
|
```json
|
|
324
328
|
{
|
|
325
|
-
"authorUser": 1, // id of a author user. Optional in case of 'enableUsers: true' in the plugin configuration
|
|
326
329
|
"content": "My sample response"
|
|
327
330
|
}
|
|
328
331
|
```
|
|
@@ -344,7 +347,7 @@ Updates a specified Comment content based on it `commentId` and related to speci
|
|
|
344
347
|
|
|
345
348
|
*GraphQL equivalent: [Public GraphQL API -> Delete Comment](#delete-comment-1)*
|
|
346
349
|
|
|
347
|
-
`DELETE <host>/comments/api::<collection name>.<content type name>:<entity id>/<commentId>?authorId=<authorId>`
|
|
350
|
+
`DELETE <host>/api/comments/api::<collection name>.<content type name>:<entity id>/<commentId>?authorId=<authorId>`
|
|
348
351
|
|
|
349
352
|
Deletes a specified Comment based on it `commentId` and related to specified instance of Content Type like for example `Page` with `ID: 1`.
|
|
350
353
|
|
|
@@ -367,7 +370,7 @@ Deletes a specified Comment based on it `commentId` and related to specified ins
|
|
|
367
370
|
|
|
368
371
|
*GraphQL equivalent: [Public GraphQL API -> Issue Abuse Report against specified Comment](#issue-abuse-report-against-specified-comment-1)*
|
|
369
372
|
|
|
370
|
-
`POST <host>/comments/api::<collection name>.<content type name>:<entity id>/comment/<commentId>/report-abuse`
|
|
373
|
+
`POST <host>/api/comments/api::<collection name>.<content type name>:<entity id>/comment/<commentId>/report-abuse`
|
|
371
374
|
|
|
372
375
|
Reports abuse in specified Comment content based on it `commentId` and related to specified instance of Content Type like for example `Page` with `ID: 1` and requests moderator attention.
|
|
373
376
|
|
|
@@ -398,7 +401,11 @@ Reports abuse in specified Comment content based on it `commentId` and related t
|
|
|
398
401
|
|
|
399
402
|
## 🕸️ Public GraphQL specification
|
|
400
403
|
|
|
401
|
-
|
|
404
|
+
**Strapi Users vs. Generic authors**
|
|
405
|
+
> Keep in mind that if you're using auth / authz your requests to setup proper user contexts it has got higher priority in order to take author data comparing to `author` property provided as part of your payload.
|
|
406
|
+
|
|
407
|
+
**Testing**
|
|
408
|
+
> To test all queries and understand the schemas use GraphQL Playground exposed by `@strapi/plugin-graphql` on `http://localhost:1337/graphql`
|
|
402
409
|
|
|
403
410
|
### Get Comments
|
|
404
411
|
|
|
@@ -516,7 +523,7 @@ mutation createComment {
|
|
|
516
523
|
relation: "api::page.page:1"
|
|
517
524
|
content: "Hello World!"
|
|
518
525
|
threadOf: 3
|
|
519
|
-
author: { id: "12345678", name: "John Wick", email: "test@test.pl" }
|
|
526
|
+
author: { id: "12345678", name: "John Wick", email: "test@test.pl" } # Optional if using auth / authz requests
|
|
520
527
|
}
|
|
521
528
|
) {
|
|
522
529
|
id
|
|
@@ -565,7 +572,7 @@ mutation updateComment {
|
|
|
565
572
|
id: 34
|
|
566
573
|
relation: "api::page.page:1"
|
|
567
574
|
content: "I've changed it!"
|
|
568
|
-
author: { id: "12345678" }
|
|
575
|
+
author: { id: "12345678" } # Optional if using auth / authz requests
|
|
569
576
|
}
|
|
570
577
|
) {
|
|
571
578
|
id
|
|
@@ -614,7 +621,11 @@ mutation updateComment {
|
|
|
614
621
|
```graphql
|
|
615
622
|
mutation removeComment {
|
|
616
623
|
removeComment(
|
|
617
|
-
input: {
|
|
624
|
+
input: {
|
|
625
|
+
id: 33,
|
|
626
|
+
relation: "api::page.page:1",
|
|
627
|
+
author: { id: "12345678" } # Optional if using auth / authz requests
|
|
628
|
+
}
|
|
618
629
|
) {
|
|
619
630
|
id
|
|
620
631
|
removed
|
|
@@ -37,7 +37,7 @@ const DiscussionThread = ({ level = [], selected = {}, allowedActions, isReloadi
|
|
|
37
37
|
{ rootThread && (<DiscussionThreadItem {...rootThread} allowedActions={allowedActions} isThreadAuthor root pinned />) }
|
|
38
38
|
{ level.map(item => {
|
|
39
39
|
const isSelected = selected.id === item.id;
|
|
40
|
-
const isThreadAuthor = !isNil(selected?.threadOf?.
|
|
40
|
+
const isThreadAuthor = !isNil(selected?.threadOf?.author?.id) && (selected?.threadOf?.author?.id === item?.author?.id);
|
|
41
41
|
return (<DiscussionThreadItem
|
|
42
42
|
key={`comment-${item.id}`}
|
|
43
43
|
{...item}
|
|
@@ -32,8 +32,7 @@ const DiscussionThreadItem = (props) => {
|
|
|
32
32
|
blockedThread,
|
|
33
33
|
gotThread,
|
|
34
34
|
threadFirstItemId,
|
|
35
|
-
|
|
36
|
-
author } = props;
|
|
35
|
+
author = {} } = props;
|
|
37
36
|
|
|
38
37
|
let background = 'neutral100';
|
|
39
38
|
let borderColor = 'neutral200';
|
|
@@ -57,8 +56,7 @@ const DiscussionThreadItem = (props) => {
|
|
|
57
56
|
StyledComponent = DiscussionThreadItemRoot;
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
const avatar = author
|
|
61
|
-
const name = author?.name || `${authorUser?.firstName} ${authorUser?.lastName}`;
|
|
59
|
+
const { name, avatar } = author;
|
|
62
60
|
const isBlocked = blocked || blockedThread;
|
|
63
61
|
|
|
64
62
|
return (<StyledComponent as="li">
|
package/content-types/comment.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-plugin-comments",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.2",
|
|
4
4
|
"description": "Strapi - Comments plugin",
|
|
5
5
|
"strapi": {
|
|
6
6
|
"name": "comments",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"kind": "plugin"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
+
"publish": "npm publish --tag latest",
|
|
11
12
|
"test:unit": "jest --verbose --coverage"
|
|
12
13
|
},
|
|
13
14
|
"repository": {
|
|
@@ -15,8 +16,8 @@
|
|
|
15
16
|
"url": "https://github.com/VirtusLab-Open-Source/strapi-plugin-comments"
|
|
16
17
|
},
|
|
17
18
|
"dependencies": {
|
|
18
|
-
"@strapi/utils": "^4.0.
|
|
19
|
-
"@strapi/plugin-graphql": "^4.0.
|
|
19
|
+
"@strapi/utils": "^4.0.6",
|
|
20
|
+
"@strapi/plugin-graphql": "^4.0.6",
|
|
20
21
|
"bad-words": "^3.0.4"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
@@ -82,14 +82,19 @@ module.exports = {
|
|
|
82
82
|
},
|
|
83
83
|
|
|
84
84
|
async removeComment(ctx) {
|
|
85
|
-
const {
|
|
86
|
-
|
|
85
|
+
const {
|
|
86
|
+
params: { relationId, commentId },
|
|
87
|
+
query: { authorId },
|
|
88
|
+
state: { user },
|
|
89
|
+
} = ctx;
|
|
90
|
+
if (authorId || user?.id) {
|
|
87
91
|
try {
|
|
88
92
|
return await this.getService()
|
|
89
93
|
.markAsRemoved(
|
|
90
94
|
commentId,
|
|
91
95
|
relationId,
|
|
92
96
|
authorId,
|
|
97
|
+
user
|
|
93
98
|
);
|
|
94
99
|
} catch (e) {
|
|
95
100
|
if (!e.isBoom) {
|
|
@@ -10,11 +10,16 @@ module.exports = ({ nexus }) => {
|
|
|
10
10
|
args: {
|
|
11
11
|
input: nonNull('CreateReport'),
|
|
12
12
|
},
|
|
13
|
-
async resolve(obj, args) {
|
|
13
|
+
async resolve(obj, args, ctx) {
|
|
14
14
|
const { input } = args;
|
|
15
|
+
const { state: { user } = {} } = ctx;
|
|
15
16
|
const { commentId, relation, ...body } = input;
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
try {
|
|
18
|
+
return await getPluginService('client')
|
|
19
|
+
.reportAbuse(commentId, relation, body, user);
|
|
20
|
+
} catch(e) {
|
|
21
|
+
throw new Error(e);
|
|
22
|
+
}
|
|
18
23
|
},
|
|
19
24
|
};
|
|
20
25
|
}
|
|
@@ -10,11 +10,16 @@ module.exports = ({ nexus }) => {
|
|
|
10
10
|
args: {
|
|
11
11
|
input: nonNull('CreateComment'),
|
|
12
12
|
},
|
|
13
|
-
async resolve(obj, args) {
|
|
13
|
+
async resolve(obj, args, ctx) {
|
|
14
14
|
const { input } = args;
|
|
15
|
+
const { state: { user } = {} } = ctx;
|
|
15
16
|
const { relation, ...body } = input;
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
try {
|
|
18
|
+
return await getPluginService('client')
|
|
19
|
+
.create(relation, body, user);
|
|
20
|
+
} catch(e) {
|
|
21
|
+
throw new Error(e);
|
|
22
|
+
}
|
|
18
23
|
},
|
|
19
24
|
};
|
|
20
25
|
}
|
|
@@ -10,11 +10,16 @@ module.exports = ({ nexus }) => {
|
|
|
10
10
|
args: {
|
|
11
11
|
input: nonNull('RemoveComment'),
|
|
12
12
|
},
|
|
13
|
-
async resolve(obj, args) {
|
|
13
|
+
async resolve(obj, args, ctx) {
|
|
14
14
|
const { input } = args;
|
|
15
|
+
const { state: { user } = {} } = ctx;
|
|
15
16
|
const { id, relation, author } = input;
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
try {
|
|
18
|
+
return await getPluginService('client')
|
|
19
|
+
.markAsRemoved(id, relation, author?.id, user);
|
|
20
|
+
} catch(e) {
|
|
21
|
+
throw new Error(e);
|
|
22
|
+
}
|
|
18
23
|
},
|
|
19
24
|
};
|
|
20
25
|
}
|
|
@@ -10,11 +10,16 @@ module.exports = ({ nexus }) => {
|
|
|
10
10
|
args: {
|
|
11
11
|
input: nonNull('UpdateComment'),
|
|
12
12
|
},
|
|
13
|
-
async resolve(obj, args) {
|
|
13
|
+
async resolve(obj, args, ctx) {
|
|
14
14
|
const { input } = args;
|
|
15
|
+
const { state: { user } = {} } = ctx;
|
|
15
16
|
const { id, relation, ...body } = input;
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
try {
|
|
18
|
+
return await getPluginService('client')
|
|
19
|
+
.update(id, relation, body, user);
|
|
20
|
+
} catch(e) {
|
|
21
|
+
throw new Error(e);
|
|
22
|
+
}
|
|
18
23
|
},
|
|
19
24
|
};
|
|
20
25
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
3
|
+
const { getPluginService } = require("../utils/functions");
|
|
4
|
+
|
|
5
|
+
module.exports = () => {
|
|
6
|
+
const auth = getPluginService('common').getConfig('gql.auth', false);
|
|
7
|
+
return {
|
|
8
|
+
'Query.findAllFlat': { auth },
|
|
9
|
+
'Query.findAllInHierarchy': { auth },
|
|
10
|
+
'Mutation.createComment': { auth },
|
|
11
|
+
'Mutation.updateComment': { auth },
|
|
12
|
+
'Mutation.removeComment': { auth },
|
|
13
|
+
'Mutation.createAbuseReport': { auth },
|
|
14
|
+
};
|
|
15
|
+
};
|
|
@@ -8,7 +8,6 @@ module.exports = ({ nexus }) => nexus.objectType({
|
|
|
8
8
|
t.string("approvalStatus")
|
|
9
9
|
t.field("threadOf", { type: 'CommentSingle' })
|
|
10
10
|
t.list.field("children", { type: 'CommentNested' })
|
|
11
|
-
t.field("authorUser", { type: 'CommentAuthorUser' })
|
|
12
11
|
t.field("author", { type: 'CommentAuthor' })
|
|
13
12
|
t.string("createdAt")
|
|
14
13
|
t.string("updatedAt")
|
|
@@ -8,7 +8,6 @@ module.exports = ({ nexus }) => nexus.objectType({
|
|
|
8
8
|
t.boolean("removed")
|
|
9
9
|
t.string("approvalStatus")
|
|
10
10
|
t.field("threadOf", { type: 'CommentSingle' })
|
|
11
|
-
t.field("authorUser", { type: 'CommentAuthorUser' })
|
|
12
11
|
t.field("author", { type: 'CommentAuthor' })
|
|
13
12
|
t.string("createdAt")
|
|
14
13
|
t.string("updatedAt")
|
package/server/services/admin.js
CHANGED
|
@@ -117,19 +117,24 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
|
|
|
117
117
|
}
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
-
const
|
|
121
|
-
where: {
|
|
122
|
-
id,
|
|
123
|
-
},
|
|
120
|
+
const defaultPopulate = {
|
|
124
121
|
populate: {
|
|
125
122
|
authorUser: true,
|
|
126
123
|
threadOf: {
|
|
127
124
|
populate: {
|
|
125
|
+
authorUser: true,
|
|
128
126
|
...reportsPopulation
|
|
129
127
|
},
|
|
130
128
|
},
|
|
131
129
|
...reportsPopulation,
|
|
132
130
|
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const entity = await strapi.db.query(getModelUid('comment')).findOne({
|
|
134
|
+
where: {
|
|
135
|
+
id,
|
|
136
|
+
},
|
|
137
|
+
...defaultPopulate,
|
|
133
138
|
});
|
|
134
139
|
|
|
135
140
|
if (!entity){
|
|
@@ -159,9 +164,7 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
|
|
|
159
164
|
threadOf: levelThreadId,
|
|
160
165
|
related: entity.related,
|
|
161
166
|
},
|
|
162
|
-
|
|
163
|
-
...reportsPopulation,
|
|
164
|
-
},
|
|
167
|
+
...defaultPopulate,
|
|
165
168
|
startingFromId: levelThreadId,
|
|
166
169
|
isAdmin: true
|
|
167
170
|
}, false);
|
|
@@ -9,7 +9,6 @@ const {
|
|
|
9
9
|
getModelUid,
|
|
10
10
|
getRelatedGroups,
|
|
11
11
|
checkBadWords,
|
|
12
|
-
isValidUserContext,
|
|
13
12
|
resolveUserContextError,
|
|
14
13
|
} = require('./utils/functions');
|
|
15
14
|
const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
|
|
@@ -49,7 +48,7 @@ module.exports = ({ strapi }) => ({
|
|
|
49
48
|
id: threadOf,
|
|
50
49
|
related: relation,
|
|
51
50
|
}) : true;
|
|
52
|
-
const validContext = isValidUserContext(user);
|
|
51
|
+
const validContext = this.getCommonService().isValidUserContext(user);
|
|
53
52
|
|
|
54
53
|
if (!linkToThread) {
|
|
55
54
|
throw new PluginError(400, 'Thread does not exist');
|
|
@@ -60,15 +59,11 @@ module.exports = ({ strapi }) => ({
|
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
if (checkBadWords(content) && singleRelationFulfilled) {
|
|
63
|
-
const { author
|
|
62
|
+
const { author = {}, ...rest } = data;
|
|
64
63
|
let authorData = {};
|
|
65
64
|
if (validContext && user) {
|
|
66
65
|
authorData = {
|
|
67
|
-
authorUser: user?.id
|
|
68
|
-
};
|
|
69
|
-
} else if (authorUser) {
|
|
70
|
-
authorData = {
|
|
71
|
-
authorUser,
|
|
66
|
+
authorUser: user?.id,
|
|
72
67
|
};
|
|
73
68
|
} else {
|
|
74
69
|
if (author.email && !REGEX.email.test(author.email)) {
|
|
@@ -83,8 +78,8 @@ module.exports = ({ strapi }) => ({
|
|
|
83
78
|
};
|
|
84
79
|
}
|
|
85
80
|
|
|
86
|
-
if (!isEmpty(authorData) && !authorData.authorId) {
|
|
87
|
-
throw new PluginError(400, '
|
|
81
|
+
if (!isEmpty(authorData) && !(authorData.authorId || authorData.authorUser)) {
|
|
82
|
+
throw new PluginError(400, `Not able to recognise author of a comment. Make sure you've provided "author" property in a payload or authenticated your request properly.`);
|
|
88
83
|
}
|
|
89
84
|
|
|
90
85
|
if (isApprovalFlowEnabled &&
|
|
@@ -98,7 +93,10 @@ module.exports = ({ strapi }) => ({
|
|
|
98
93
|
...authorData,
|
|
99
94
|
related: relation,
|
|
100
95
|
approvalStatus: isApprovalFlowEnabled ? APPROVAL_STATUS.PENDING : null,
|
|
101
|
-
}
|
|
96
|
+
},
|
|
97
|
+
populate: {
|
|
98
|
+
authorUser: true,
|
|
99
|
+
},
|
|
102
100
|
});
|
|
103
101
|
return this.getCommonService().sanitizeCommentEntity({
|
|
104
102
|
...entity,
|
|
@@ -109,20 +107,21 @@ module.exports = ({ strapi }) => ({
|
|
|
109
107
|
},
|
|
110
108
|
|
|
111
109
|
// Update a comment
|
|
112
|
-
async update(id, relation, data, user) {
|
|
110
|
+
async update(id, relation, data, user = undefined) {
|
|
113
111
|
const { content } = data;
|
|
114
112
|
|
|
115
113
|
const singleRelationFulfilled = relation && REGEX.relatedUid.test(relation)
|
|
116
114
|
|
|
117
115
|
if(!singleRelationFulfilled) {
|
|
118
|
-
throw new PluginError(400, `
|
|
116
|
+
throw new PluginError(400, `Request property "relation" got incorrect format, use format like "api::<collection name>.<content type name>:<entity id>"`);
|
|
119
117
|
}
|
|
120
118
|
|
|
121
119
|
const existingEntity = await this.getCommonService().findOne({
|
|
122
120
|
id,
|
|
123
121
|
related: relation,
|
|
124
122
|
});
|
|
125
|
-
|
|
123
|
+
|
|
124
|
+
const validContext = this.getCommonService().isValidUserContext(user);
|
|
126
125
|
|
|
127
126
|
if (!validContext) {
|
|
128
127
|
throw resolveUserContextError(user);
|
|
@@ -133,17 +132,17 @@ module.exports = ({ strapi }) => ({
|
|
|
133
132
|
const entity = await strapi.db.query(getModelUid('comment')).update({
|
|
134
133
|
where: { id },
|
|
135
134
|
data: { content },
|
|
136
|
-
populate: { threadOf: true },
|
|
135
|
+
populate: { threadOf: true, authorUser: true },
|
|
137
136
|
});
|
|
138
137
|
return this.getCommonService().sanitizeCommentEntity(entity);
|
|
139
138
|
}
|
|
140
139
|
}
|
|
141
|
-
throw new PluginError(
|
|
140
|
+
throw new PluginError(403, `You're not allowed to take an action on that entity. Make sure you've provided "author" property in a payload or authenticated your request properly.`);
|
|
142
141
|
},
|
|
143
142
|
|
|
144
143
|
// Report abuse in comment
|
|
145
|
-
async reportAbuse(id, relation, payload, user) {
|
|
146
|
-
if (!isValidUserContext(user)) {
|
|
144
|
+
async reportAbuse(id, relation, payload, user = undefined) {
|
|
145
|
+
if (!this.getCommonService().isValidUserContext(user)) {
|
|
147
146
|
throw resolveUserContextError(user);
|
|
148
147
|
}
|
|
149
148
|
const reportAgainstEntity = await this.getCommonService().findOne({
|
|
@@ -173,18 +172,34 @@ module.exports = ({ strapi }) => ({
|
|
|
173
172
|
throw new PluginError(500, 'Report cannot be created');
|
|
174
173
|
}
|
|
175
174
|
}
|
|
176
|
-
throw new PluginError(
|
|
175
|
+
throw new PluginError(403, `You're not allowed to take an action on that entity. Make sure that comment exist or you've authenticated your request properly.`);
|
|
177
176
|
},
|
|
178
177
|
|
|
179
|
-
async markAsRemoved(id, relation, authorId){
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
178
|
+
async markAsRemoved(id, relation, authorId, user = undefined){
|
|
179
|
+
if (!this.getCommonService().isValidUserContext(user)) {
|
|
180
|
+
throw resolveUserContextError(user);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const author = user?.id || authorId;
|
|
185
184
|
|
|
186
|
-
if (!
|
|
187
|
-
throw new PluginError(
|
|
185
|
+
if (!author) {
|
|
186
|
+
throw new PluginError(403, `You're not allowed to take an action on that entity. Make sure that you've provided proper "authorId" or authenticated your request properly.`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let entity;
|
|
190
|
+
try {
|
|
191
|
+
const byAuthor = user?.id ? {
|
|
192
|
+
authorUser: author
|
|
193
|
+
} : {
|
|
194
|
+
authorId: author
|
|
195
|
+
}
|
|
196
|
+
entity = await this.getCommonService().findOne({
|
|
197
|
+
id,
|
|
198
|
+
related: relation,
|
|
199
|
+
...byAuthor,
|
|
200
|
+
});
|
|
201
|
+
} catch(e) {
|
|
202
|
+
throw new PluginError(404, `Entity does not exist or you're not allowed to take an action on it`);
|
|
188
203
|
}
|
|
189
204
|
|
|
190
205
|
const removedEntity = await strapi.db.query(getModelUid('comment'))
|
|
@@ -194,7 +209,7 @@ module.exports = ({ strapi }) => ({
|
|
|
194
209
|
related: relation
|
|
195
210
|
},
|
|
196
211
|
data: { removed: true },
|
|
197
|
-
populate: { threadOf: true },
|
|
212
|
+
populate: { threadOf: true, authorUser: true },
|
|
198
213
|
})
|
|
199
214
|
await this.markAsRemovedNested(id, true);
|
|
200
215
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { isArray, isNumber, isObject, first, parseInt } = require('lodash');
|
|
3
|
+
const { isArray, isNumber, isObject, isNil, first, parseInt } = require('lodash');
|
|
4
4
|
// const { sanitizeEntity } = require('strapi-utils');
|
|
5
5
|
const PluginError = require('./../utils/error');
|
|
6
6
|
const {
|
|
@@ -86,8 +86,12 @@ module.exports = ({ strapi }) => ({
|
|
|
86
86
|
async findOne(criteria) {
|
|
87
87
|
const entity = await strapi.db.query(getModelUid('comment'))
|
|
88
88
|
.findOne({
|
|
89
|
-
where: criteria
|
|
90
|
-
|
|
89
|
+
where: criteria,
|
|
90
|
+
populate: {
|
|
91
|
+
reports: true,
|
|
92
|
+
authorUser: true,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
91
95
|
if (!entity){
|
|
92
96
|
throw new PluginError(404, 'Not found');
|
|
93
97
|
}
|
|
@@ -154,15 +158,16 @@ module.exports = ({ strapi }) => ({
|
|
|
154
158
|
}
|
|
155
159
|
},
|
|
156
160
|
|
|
157
|
-
sanitizeCommentEntity
|
|
158
|
-
|
|
159
|
-
...
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
sanitizeCommentEntity(entity) {
|
|
162
|
+
return {
|
|
163
|
+
...buildAuthorModel({
|
|
164
|
+
...entity,
|
|
165
|
+
threadOf: isObject(entity.threadOf) ? buildAuthorModel(entity.threadOf) : entity.threadOf,
|
|
166
|
+
}),
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
isValidUserContext(user) {
|
|
171
|
+
return user ? !isNil(user?.id) : true;
|
|
172
|
+
},
|
|
168
173
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const BadWordsFilter = require('bad-words');
|
|
2
2
|
const PluginError = require('./../../utils/error');
|
|
3
3
|
const { REGEX } = require('./../../utils/constants')
|
|
4
|
-
const { first, get, isObject,
|
|
4
|
+
const { first, get, isObject, isEmpty } = require('lodash');
|
|
5
5
|
|
|
6
6
|
const buildNestedStructure = (
|
|
7
7
|
entities,
|
|
@@ -33,7 +33,7 @@ const buildNestedStructure = (
|
|
|
33
33
|
|
|
34
34
|
module.exports = {
|
|
35
35
|
isEqualEntity: (existing, data, user) => {
|
|
36
|
-
const {
|
|
36
|
+
const { author: existingAuthor } = existing;
|
|
37
37
|
const { author } = data;
|
|
38
38
|
|
|
39
39
|
// Disallow approval status change by Client
|
|
@@ -42,10 +42,10 @@ module.exports = {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// Make sure that author is exact the same
|
|
45
|
-
if (
|
|
46
|
-
const existingUserId =
|
|
47
|
-
const receivedUserId = user?.id || author;
|
|
48
|
-
return existingUserId === receivedUserId;
|
|
45
|
+
if (user) {
|
|
46
|
+
const existingUserId = existingAuthor?.id || existingAuthor;
|
|
47
|
+
const receivedUserId = user?.id || author?.id;
|
|
48
|
+
return receivedUserId && (existingUserId === receivedUserId);
|
|
49
49
|
}
|
|
50
50
|
return existingAuthor.id === author?.id;
|
|
51
51
|
},
|
|
@@ -85,22 +85,27 @@ module.exports = {
|
|
|
85
85
|
|
|
86
86
|
buildNestedStructure,
|
|
87
87
|
|
|
88
|
-
isValidUserContext: (user = {}) => {
|
|
89
|
-
const builtInContextEnabled = get(strapi.config, 'plugins.comments.enableUsers', false);
|
|
90
|
-
return builtInContextEnabled ? !isNil(user.id) : true;
|
|
91
|
-
},
|
|
92
|
-
|
|
93
88
|
buildAuthorModel: (item) => {
|
|
94
89
|
const { authorUser, authorId, authorName, authorEmail, authorAvatar, ...rest } = item;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
90
|
+
let author = {};
|
|
91
|
+
if (authorUser) {
|
|
92
|
+
author = {
|
|
93
|
+
id: authorUser.id,
|
|
94
|
+
name: authorUser.username,
|
|
95
|
+
email: authorUser.email,
|
|
96
|
+
avatar: authorUser.avatar,
|
|
97
|
+
};
|
|
98
|
+
} else if(authorId) {
|
|
99
|
+
author = {
|
|
100
|
+
id: authorId,
|
|
101
|
+
name: authorName,
|
|
102
|
+
email: authorEmail,
|
|
103
|
+
avatar: authorAvatar,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
101
106
|
return {
|
|
102
107
|
...rest,
|
|
103
|
-
author,
|
|
108
|
+
author: isEmpty(author) ? undefined : author,
|
|
104
109
|
};
|
|
105
110
|
},
|
|
106
111
|
|