strapi-plugin-comments 2.0.2 → 2.0.6
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/.husky/pre-commit +4 -0
- package/README.md +31 -8
- package/__mocks__/initSetup.js +31 -2
- package/admin/src/components/FormSwitch/index.js +18 -0
- package/admin/src/components/FormSwitch/styles.js +8 -0
- package/admin/src/hooks/useConfig.js +49 -0
- package/admin/src/index.js +31 -2
- package/admin/src/pages/Details/index.js +5 -2
- package/admin/src/pages/Settings/components/RestartAlert/styles.js +8 -0
- package/admin/src/pages/Settings/index.js +392 -0
- package/admin/src/pages/Settings/utils/api.js +60 -0
- package/admin/src/permissions.js +3 -0
- package/admin/src/translations/en.json +46 -1
- package/admin/src/utils/handleAPIError.js +3 -2
- package/admin/src/utils/index.js +2 -1
- package/admin/src/utils/parseRegExp.js +10 -0
- package/package.json +10 -7
- package/server/bootstrap.js +24 -0
- package/server/config/index.js +1 -0
- package/server/controllers/admin.js +42 -8
- package/server/controllers/client.js +11 -2
- package/server/controllers/utils/__tests__/parsers.test.js +94 -0
- package/server/controllers/utils/parsers.js +15 -13
- package/server/graphql/index.js +7 -8
- package/server/graphql/queries/findAllFlat.js +1 -1
- package/server/graphql/resolvers-config.js +2 -4
- package/server/graphql/types/index.js +3 -0
- package/server/graphql/types/report-reason.js +10 -10
- package/server/graphql/types/response-find-all.js +7 -0
- package/server/graphql/types/response-meta.js +6 -0
- package/server/graphql/types/response-pagination.js +11 -0
- package/server/register.js +1 -3
- package/server/routes/admin.js +32 -0
- package/server/services/__tests__/common.test.js +95 -14
- package/server/services/admin.js +57 -6
- package/server/services/client.js +14 -8
- package/server/services/common.js +92 -15
- package/server/services/utils/__tests__/functions.test.js +12 -1
- package/server/services/utils/functions.js +9 -1
- package/server/utils/constants.js +9 -0
- package/strapi-server.js +12 -5
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
getRelatedGroups,
|
|
11
11
|
resolveUserContextError,
|
|
12
12
|
} = require('./utils/functions');
|
|
13
|
-
const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
|
|
13
|
+
const { APPROVAL_STATUS, REGEX, CONFIG_PARAMS } = require('./../utils/constants')
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Comments Plugin - Client services
|
|
@@ -31,16 +31,15 @@ module.exports = ({ strapi }) => ({
|
|
|
31
31
|
throw new PluginError(400, `Field "related" got incorrect format, use format like "api::<collection name>.<content type name>:<entity id>"`);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const [ uid,
|
|
35
|
-
const parsedRelatedId = parseInt(relatedStringId);
|
|
36
|
-
const relatedId = isNumber(parsedRelatedId) ? parsedRelatedId : relatedStringId;
|
|
34
|
+
const [ uid, relatedId ] = await this.getCommonService().parseRelationString(relation);
|
|
37
35
|
|
|
38
36
|
const relatedEntity = await strapi.db.query(uid).findOne(relatedId);
|
|
39
37
|
if (!relatedEntity) {
|
|
40
38
|
throw new PluginError(400, `Relation for field "related" does not exist. Check your payload please.`);
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
const
|
|
41
|
+
const approvalFlow = await this.getCommonService().getConfig(CONFIG_PARAMS.APPROVAL_FLOW, []);
|
|
42
|
+
const isApprovalFlowEnabled = approvalFlow.includes(uid) ||
|
|
44
43
|
relatedEntity.requireCommentsApproval;
|
|
45
44
|
|
|
46
45
|
const linkToThread = !isNil(threadOf) ? await this.getCommonService().findOne({
|
|
@@ -57,7 +56,7 @@ module.exports = ({ strapi }) => ({
|
|
|
57
56
|
throw resolveUserContextError(user);
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
if (this.getCommonService().checkBadWords(content) && singleRelationFulfilled) {
|
|
59
|
+
if (await this.getCommonService().checkBadWords(content) && singleRelationFulfilled) {
|
|
61
60
|
const { author = {}, ...rest } = data;
|
|
62
61
|
let authorData = {};
|
|
63
62
|
if (validContext && user) {
|
|
@@ -115,6 +114,8 @@ module.exports = ({ strapi }) => ({
|
|
|
115
114
|
throw new PluginError(400, `Request property "relation" got incorrect format, use format like "api::<collection name>.<content type name>:<entity id>"`);
|
|
116
115
|
}
|
|
117
116
|
|
|
117
|
+
await this.getCommonService().parseRelationString(relation);
|
|
118
|
+
|
|
118
119
|
const existingEntity = await this.getCommonService().findOne({
|
|
119
120
|
id,
|
|
120
121
|
related: relation,
|
|
@@ -127,7 +128,7 @@ module.exports = ({ strapi }) => ({
|
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
if (isEqualEntity(existingEntity, data, user) && content) {
|
|
130
|
-
if (this.getCommonService().checkBadWords(content)) {
|
|
131
|
+
if (await this.getCommonService().checkBadWords(content)) {
|
|
131
132
|
const entity = await strapi.db.query(getModelUid('comment')).update({
|
|
132
133
|
where: { id },
|
|
133
134
|
data: { content },
|
|
@@ -144,6 +145,9 @@ module.exports = ({ strapi }) => ({
|
|
|
144
145
|
if (!this.getCommonService().isValidUserContext(user)) {
|
|
145
146
|
throw resolveUserContextError(user);
|
|
146
147
|
}
|
|
148
|
+
|
|
149
|
+
await this.getCommonService().parseRelationString(relation);
|
|
150
|
+
|
|
147
151
|
const reportAgainstEntity = await this.getCommonService().findOne({
|
|
148
152
|
id,
|
|
149
153
|
related: relation,
|
|
@@ -185,6 +189,8 @@ module.exports = ({ strapi }) => ({
|
|
|
185
189
|
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.`);
|
|
186
190
|
}
|
|
187
191
|
|
|
192
|
+
await this.getCommonService().parseRelationString(relation);
|
|
193
|
+
|
|
188
194
|
let entity;
|
|
189
195
|
try {
|
|
190
196
|
const byAuthor = user?.id ? {
|
|
@@ -216,7 +222,7 @@ module.exports = ({ strapi }) => ({
|
|
|
216
222
|
},
|
|
217
223
|
|
|
218
224
|
async sendAbuseReportEmail(reason, content) {
|
|
219
|
-
const rolesToBeNotified = this.getCommonService().getConfig(
|
|
225
|
+
const rolesToBeNotified = await this.getCommonService().getConfig(CONFIG_PARAMS.MODERATOR_ROLES) || ['strapi-super-admin'];
|
|
220
226
|
|
|
221
227
|
const adminUserModel = strapi.db.query('admin::user');
|
|
222
228
|
const emails = await adminUserModel.findMany({
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const BadWordsFilter = require('bad-words');
|
|
4
|
-
const { isArray, isNumber, isObject, isNil, isString, first, parseInt, set } = require('lodash');
|
|
5
|
-
const { REGEX } = require('../utils/constants');
|
|
4
|
+
const { isArray, isNumber, isObject, isNil, isString, first, parseInt, set, get } = require('lodash');
|
|
5
|
+
const { REGEX, CONFIG_PARAMS } = require('../utils/constants');
|
|
6
6
|
const PluginError = require('./../utils/error');
|
|
7
7
|
const {
|
|
8
8
|
getModelUid,
|
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
buildNestedStructure,
|
|
11
11
|
filterOurResolvedReports,
|
|
12
12
|
buildAuthorModel,
|
|
13
|
+
buildConfigQueryProp,
|
|
13
14
|
} = require('./utils/functions');
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -18,11 +19,26 @@ const {
|
|
|
18
19
|
|
|
19
20
|
module.exports = ({ strapi }) => ({
|
|
20
21
|
|
|
21
|
-
getConfig(prop, defaultValue) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
async getConfig(prop, defaultValue, useLocal = false) {
|
|
23
|
+
const queryProp = buildConfigQueryProp(prop);
|
|
24
|
+
const pluginStore = await this.getPluginStore();
|
|
25
|
+
const config = await pluginStore.get({ key: 'config' });
|
|
26
|
+
|
|
27
|
+
let result;
|
|
28
|
+
if (config && !useLocal) {
|
|
29
|
+
result = queryProp ? get(config, queryProp, defaultValue) : config;
|
|
30
|
+
} else {
|
|
31
|
+
result = this.getLocalConfig(queryProp, defaultValue);
|
|
25
32
|
}
|
|
33
|
+
return isNil(result) ? defaultValue : result;
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
async getPluginStore() {
|
|
37
|
+
return strapi.store({ type: 'plugin', name: 'comments' });
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
getLocalConfig(prop, defaultValue) {
|
|
41
|
+
const queryProp = buildConfigQueryProp(prop);
|
|
26
42
|
const result = strapi.config.get(`plugin.comments${ queryProp ? '.' + queryProp : ''}`);
|
|
27
43
|
return isNil(result) ? defaultValue : result;
|
|
28
44
|
},
|
|
@@ -52,12 +68,35 @@ module.exports = ({ strapi }) => ({
|
|
|
52
68
|
};
|
|
53
69
|
}
|
|
54
70
|
|
|
71
|
+
let meta = {};
|
|
55
72
|
if (pagination && isObject(pagination)) {
|
|
56
|
-
const
|
|
73
|
+
const parsedPagination = Object.keys(pagination).reduce((prev, curr) => ({
|
|
74
|
+
...prev,
|
|
75
|
+
[curr]: parseInt(pagination[curr]),
|
|
76
|
+
}), {});
|
|
77
|
+
const { page, pageSize = 10, limit, start = 0 } = parsedPagination
|
|
78
|
+
const paginationByPage = !isNil(page);
|
|
79
|
+
|
|
57
80
|
queryExtension = {
|
|
58
81
|
...queryExtension,
|
|
59
|
-
offset: (page - 1) * pageSize,
|
|
60
|
-
limit: pageSize,
|
|
82
|
+
offset: paginationByPage ? (page - 1) * pageSize : start,
|
|
83
|
+
limit: paginationByPage ? pageSize : limit,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const metaPagination = paginationByPage ? {
|
|
87
|
+
pagination: {
|
|
88
|
+
page,
|
|
89
|
+
pageSize,
|
|
90
|
+
},
|
|
91
|
+
} : {
|
|
92
|
+
pagination: {
|
|
93
|
+
start,
|
|
94
|
+
limit,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
meta = {
|
|
99
|
+
...metaPagination,
|
|
61
100
|
};
|
|
62
101
|
}
|
|
63
102
|
|
|
@@ -73,6 +112,26 @@ module.exports = ({ strapi }) => ({
|
|
|
73
112
|
...queryExtension,
|
|
74
113
|
});
|
|
75
114
|
|
|
115
|
+
if (pagination?.withCount && (pagination.withCount === 'true' || pagination.withCount === true)) {
|
|
116
|
+
const total = await strapi.db.query(getModelUid('comment'))
|
|
117
|
+
.count({
|
|
118
|
+
where: {
|
|
119
|
+
...query,
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
const pageCount = Math.floor(total / meta.pagination.pageSize);
|
|
123
|
+
meta = {
|
|
124
|
+
...meta,
|
|
125
|
+
pagination: {
|
|
126
|
+
...meta.pagination,
|
|
127
|
+
pageCount: !isNil(meta.pagination.page) ?
|
|
128
|
+
total % meta.pagination.pageSize === 0 ? pageCount : pageCount + 1 :
|
|
129
|
+
undefined,
|
|
130
|
+
total,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
76
135
|
const entriesWithThreads = await Promise.all(entries.map(async _ => {
|
|
77
136
|
const [nestedEntries, count] = await strapi.db.query(getModelUid('comment'))
|
|
78
137
|
.findWithCount({
|
|
@@ -96,9 +155,15 @@ module.exports = ({ strapi }) => ({
|
|
|
96
155
|
});
|
|
97
156
|
|
|
98
157
|
if (relatedEntities.filter(_ => _).length > 0) {
|
|
99
|
-
return
|
|
158
|
+
return {
|
|
159
|
+
data: result.map(_ => this.mergeRelatedEntityTo(_, relatedEntities)),
|
|
160
|
+
meta,
|
|
161
|
+
};
|
|
100
162
|
}
|
|
101
|
-
return
|
|
163
|
+
return {
|
|
164
|
+
data: result,
|
|
165
|
+
meta,
|
|
166
|
+
};
|
|
102
167
|
},
|
|
103
168
|
|
|
104
169
|
// Find comments and create relations tree structure
|
|
@@ -109,7 +174,7 @@ module.exports = ({ strapi }) => ({
|
|
|
109
174
|
startingFromId = null,
|
|
110
175
|
dropBlockedThreads = false,
|
|
111
176
|
}, relatedEntity) {
|
|
112
|
-
const entities = await this.findAllFlat({ query, populate, sort }, relatedEntity);
|
|
177
|
+
const entities = await this.findAllFlat({ query, populate, sort }, relatedEntity)?.data;
|
|
113
178
|
return buildNestedStructure(entities, startingFromId, 'threadOf', dropBlockedThreads, false);
|
|
114
179
|
},
|
|
115
180
|
|
|
@@ -124,7 +189,7 @@ module.exports = ({ strapi }) => ({
|
|
|
124
189
|
},
|
|
125
190
|
});
|
|
126
191
|
if (!entity){
|
|
127
|
-
throw new PluginError(404, '
|
|
192
|
+
throw new PluginError(404, 'Comment does not exist. Check your payload please.');
|
|
128
193
|
}
|
|
129
194
|
return filterOurResolvedReports(this.sanitizeCommentEntity(entity));
|
|
130
195
|
},
|
|
@@ -202,8 +267,20 @@ module.exports = ({ strapi }) => ({
|
|
|
202
267
|
return user ? !isNil(user?.id) : true;
|
|
203
268
|
},
|
|
204
269
|
|
|
205
|
-
|
|
206
|
-
const
|
|
270
|
+
async parseRelationString(relation) {
|
|
271
|
+
const [ uid, relatedStringId ] = getRelatedGroups(relation);
|
|
272
|
+
const parsedRelatedId = parseInt(relatedStringId);
|
|
273
|
+
const relatedId = isNumber(parsedRelatedId) ? parsedRelatedId : relatedStringId;
|
|
274
|
+
|
|
275
|
+
const enabledCollections = await this.getConfig(CONFIG_PARAMS.ENABLED_COLLECTIONS, []);
|
|
276
|
+
if (!enabledCollections.includes(uid)) {
|
|
277
|
+
throw new PluginError(403, `Action not allowed for collection '${uid}'. Use one of: ${ enabledCollections.join(', ') }`);
|
|
278
|
+
}
|
|
279
|
+
return [ uid, relatedId];
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
async checkBadWords(content) {
|
|
283
|
+
const config = await this.getConfig(CONFIG_PARAMS.BAD_WORDS, true);
|
|
207
284
|
if (config) {
|
|
208
285
|
const filter = new BadWordsFilter(isObject(config) ? config : undefined);
|
|
209
286
|
if (content && filter.isProfane(content)) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const PluginError = require('../../../utils/error');
|
|
2
|
-
const { resolveUserContextError, getModelUid } = require('../functions');
|
|
2
|
+
const { resolveUserContextError, getModelUid, buildConfigQueryProp } = require('../functions');
|
|
3
3
|
|
|
4
4
|
beforeEach(() => {
|
|
5
5
|
Object.defineProperty(global, 'strapi', {
|
|
@@ -39,4 +39,15 @@ describe('Test service functions utils', () => {
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
});
|
|
42
|
+
|
|
43
|
+
describe('Building config query props', () => {
|
|
44
|
+
test('Should return prop as is', () => {
|
|
45
|
+
expect(buildConfigQueryProp('my.sample.prop')).toBe('my.sample.prop');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('Should build query prop with \'.\'', () => {
|
|
49
|
+
expect(buildConfigQueryProp(['my', 'sample', 'prop'])).toBe('my.sample.prop');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
42
53
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const PluginError = require('./../../utils/error');
|
|
2
2
|
const { REGEX } = require('./../../utils/constants')
|
|
3
|
-
const { first, isObject, isEmpty } = require('lodash');
|
|
3
|
+
const { first, isObject, isArray, isEmpty } = require('lodash');
|
|
4
4
|
|
|
5
5
|
const buildNestedStructure = (
|
|
6
6
|
entities,
|
|
@@ -93,6 +93,14 @@ module.exports = {
|
|
|
93
93
|
};
|
|
94
94
|
},
|
|
95
95
|
|
|
96
|
+
buildConfigQueryProp(prop) {
|
|
97
|
+
let queryProp = prop;
|
|
98
|
+
if (prop && isArray(prop)) {
|
|
99
|
+
queryProp = prop.join('.');
|
|
100
|
+
}
|
|
101
|
+
return queryProp;
|
|
102
|
+
},
|
|
103
|
+
|
|
96
104
|
resolveUserContextError: user => {
|
|
97
105
|
if (user) {
|
|
98
106
|
throw new PluginError(401, 'Not authenticated');
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
const CONFIG_PARAMS = {
|
|
2
|
+
ENABLED_COLLECTIONS: 'enabledCollections',
|
|
3
|
+
APPROVAL_FLOW: 'approvalFlow',
|
|
4
|
+
ENTRY_LABEL: 'entryLabel',
|
|
5
|
+
MODERATOR_ROLES: 'moderatorRoles',
|
|
6
|
+
BAD_WORDS: 'badWords',
|
|
7
|
+
};
|
|
8
|
+
|
|
1
9
|
const APPROVAL_STATUS = {
|
|
2
10
|
PENDING: "PENDING",
|
|
3
11
|
APPROVED: "APPROVED",
|
|
@@ -12,6 +20,7 @@ const REGEX = {
|
|
|
12
20
|
};
|
|
13
21
|
|
|
14
22
|
module.exports = {
|
|
23
|
+
CONFIG_PARAMS,
|
|
15
24
|
APPROVAL_STATUS,
|
|
16
25
|
REGEX,
|
|
17
26
|
};
|
package/strapi-server.js
CHANGED
|
@@ -4,7 +4,7 @@ const { isArray, isEmpty, isString } = require('lodash');
|
|
|
4
4
|
|
|
5
5
|
const server = require('./server');
|
|
6
6
|
const contentTypes = require('./content-types');
|
|
7
|
-
const { REGEX } = require('./server/utils/constants');
|
|
7
|
+
const { REGEX, CONFIG_PARAMS } = require('./server/utils/constants');
|
|
8
8
|
|
|
9
9
|
module.exports = () => ({
|
|
10
10
|
...server,
|
|
@@ -15,24 +15,31 @@ module.exports = () => ({
|
|
|
15
15
|
...server.config.default,
|
|
16
16
|
}),
|
|
17
17
|
validator: (config) => {
|
|
18
|
+
// Check enabledCollections values
|
|
19
|
+
const enabledCollectionsValid = config[CONFIG_PARAMS.ENABLED_COLLECTIONS]
|
|
20
|
+
.filter(_ => !REGEX.uid.test(_)).length === 0;
|
|
21
|
+
if (!enabledCollectionsValid) {
|
|
22
|
+
throw new Error(`'enabledCollections' must contain Content Types identifiers in the format like 'api::<collection name>.<content type name>'`);
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
// Check moderatorRoles values
|
|
19
|
-
const moderatorRolesValid = config.
|
|
26
|
+
const moderatorRolesValid = config[CONFIG_PARAMS.MODERATOR_ROLES]
|
|
20
27
|
.filter(_ => !isString(_)).length === 0;
|
|
21
28
|
if (!moderatorRolesValid) {
|
|
22
29
|
throw new Error(`'moderatorRoles' must roles keys`);
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
// Check approvalFlow values
|
|
26
|
-
const approvalFlowValid = config.
|
|
33
|
+
const approvalFlowValid = config[CONFIG_PARAMS.APPROVAL_FLOW]
|
|
27
34
|
.filter(_ => !REGEX.uid.test(_)).length === 0;
|
|
28
35
|
if (!approvalFlowValid) {
|
|
29
36
|
throw new Error(`'approvalFlow' must contain Content Types identifiers in the format like 'api::<collection name>.<content type name>'`);
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
// Check entryLabel keys and values
|
|
33
|
-
const entryLabelKeysValid = Object.keys(config.
|
|
40
|
+
const entryLabelKeysValid = Object.keys(config[CONFIG_PARAMS.ENTRY_LABEL])
|
|
34
41
|
.filter(_ => _ !== '*')
|
|
35
|
-
.filter(_ => !(REGEX.uid.test(_) && isArray(config.
|
|
42
|
+
.filter(_ => !(REGEX.uid.test(_) && isArray(config[CONFIG_PARAMS.ENTRY_LABEL][_]) && !isEmpty(config[CONFIG_PARAMS.ENTRY_LABEL][_]))).length === 0;
|
|
36
43
|
|
|
37
44
|
if (!entryLabelKeysValid) {
|
|
38
45
|
throw new Error(`'entryLabel' must contain records in the format like 'api::<collection name>.<content type name>': ['Field1', 'field_2']`);
|