strapi-plugin-comments 2.0.0 → 2.0.4
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 +33 -5
- package/__mocks__/initSetup.js +31 -2
- package/admin/src/hooks/useConfig.js +49 -0
- package/admin/src/index.js +31 -2
- package/admin/src/pages/Details/components/DetailsEntity/index.js +3 -1
- package/admin/src/pages/Details/index.js +7 -7
- package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +2 -2
- package/admin/src/pages/Settings/components/RestartAlert/styles.js +8 -0
- package/admin/src/pages/Settings/index.js +376 -0
- package/admin/src/pages/Settings/utils/api.js +60 -0
- package/admin/src/permissions.js +3 -0
- package/admin/src/translations/en.json +44 -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 +8 -5
- 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 +4 -4
- package/server/controllers/utils/parsers.js +3 -1
- package/server/graphql/index.js +7 -8
- package/server/graphql/queries/findAllFlat.js +4 -2
- package/server/graphql/queries/findAllInHierarchy.js +3 -3
- package/server/graphql/resolvers-config.js +2 -4
- package/server/graphql/types/report-reason.js +10 -10
- 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 +69 -11
- package/server/services/utils/__tests__/functions.test.js +12 -1
- package/server/services/utils/functions.js +9 -1
- package/server/utils/constants.js +10 -0
- package/strapi-server.js +12 -5
- package/.circleci/config.yml +0 -48
- package/.eslintrc +0 -35
- package/.github/pull_request_template.md +0 -13
- package/.nvmrc +0 -1
- package/codecov.yml +0 -4
- package/public/assets/logo.png +0 -0
- package/public/assets/preview.png +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const BadWordsFilter = require('bad-words');
|
|
4
|
-
const { isArray, isNumber, isObject, isNil, first, parseInt } = require('lodash');
|
|
4
|
+
const { isArray, isNumber, isObject, isNil, isString, first, parseInt, set, get } = require('lodash');
|
|
5
|
+
const { REGEX, CONFIG_PARAMS } = require('../utils/constants');
|
|
5
6
|
const PluginError = require('./../utils/error');
|
|
6
7
|
const {
|
|
7
8
|
getModelUid,
|
|
@@ -9,6 +10,7 @@ const {
|
|
|
9
10
|
buildNestedStructure,
|
|
10
11
|
filterOurResolvedReports,
|
|
11
12
|
buildAuthorModel,
|
|
13
|
+
buildConfigQueryProp,
|
|
12
14
|
} = require('./utils/functions');
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -17,22 +19,64 @@ const {
|
|
|
17
19
|
|
|
18
20
|
module.exports = ({ strapi }) => ({
|
|
19
21
|
|
|
20
|
-
getConfig(prop, defaultValue) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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);
|
|
24
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);
|
|
25
42
|
const result = strapi.config.get(`plugin.comments${ queryProp ? '.' + queryProp : ''}`);
|
|
26
43
|
return isNil(result) ? defaultValue : result;
|
|
27
44
|
},
|
|
28
45
|
|
|
29
46
|
// Find comments in the flat structure
|
|
30
|
-
async findAllFlat({
|
|
47
|
+
async findAllFlat({
|
|
48
|
+
query = {},
|
|
49
|
+
populate = {},
|
|
50
|
+
sort,
|
|
51
|
+
pagination }, relatedEntity = null) {
|
|
31
52
|
|
|
32
53
|
const defaultPopulate = {
|
|
33
54
|
authorUser: true,
|
|
34
55
|
};
|
|
35
56
|
|
|
57
|
+
let queryExtension = {};
|
|
58
|
+
|
|
59
|
+
if (sort && (isString(sort) || isArray(sort))) {
|
|
60
|
+
queryExtension = {
|
|
61
|
+
...queryExtension,
|
|
62
|
+
orderBy: (isString(sort) ? [sort] : sort)
|
|
63
|
+
.map(_ => REGEX.sorting.test(_) ? _ : `${_}:asc`)
|
|
64
|
+
.reduce((prev, curr) => {
|
|
65
|
+
const [type = 'asc', ...parts] = curr.split(':').reverse();
|
|
66
|
+
return { ...set(prev, parts.reverse().join('.'), type) };
|
|
67
|
+
}, {})
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (pagination && isObject(pagination)) {
|
|
72
|
+
const { page = 1, pageSize = 10 } = pagination;
|
|
73
|
+
queryExtension = {
|
|
74
|
+
...queryExtension,
|
|
75
|
+
offset: (page - 1) * pageSize,
|
|
76
|
+
limit: pageSize,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
36
80
|
const entries = await strapi.db.query(getModelUid('comment'))
|
|
37
81
|
.findMany({
|
|
38
82
|
where: {
|
|
@@ -42,6 +86,7 @@ module.exports = ({ strapi }) => ({
|
|
|
42
86
|
...defaultPopulate,
|
|
43
87
|
...populate,
|
|
44
88
|
},
|
|
89
|
+
...queryExtension,
|
|
45
90
|
});
|
|
46
91
|
|
|
47
92
|
const entriesWithThreads = await Promise.all(entries.map(async _ => {
|
|
@@ -76,10 +121,11 @@ module.exports = ({ strapi }) => ({
|
|
|
76
121
|
async findAllInHierarchy ({
|
|
77
122
|
query,
|
|
78
123
|
populate = {},
|
|
124
|
+
sort,
|
|
79
125
|
startingFromId = null,
|
|
80
126
|
dropBlockedThreads = false,
|
|
81
127
|
}, relatedEntity) {
|
|
82
|
-
const entities = await this.findAllFlat({ query, populate }, relatedEntity);
|
|
128
|
+
const entities = await this.findAllFlat({ query, populate, sort }, relatedEntity);
|
|
83
129
|
return buildNestedStructure(entities, startingFromId, 'threadOf', dropBlockedThreads, false);
|
|
84
130
|
},
|
|
85
131
|
|
|
@@ -94,7 +140,7 @@ module.exports = ({ strapi }) => ({
|
|
|
94
140
|
},
|
|
95
141
|
});
|
|
96
142
|
if (!entity){
|
|
97
|
-
throw new PluginError(404, '
|
|
143
|
+
throw new PluginError(404, 'Comment does not exist. Check your payload please.');
|
|
98
144
|
}
|
|
99
145
|
return filterOurResolvedReports(this.sanitizeCommentEntity(entity));
|
|
100
146
|
},
|
|
@@ -116,7 +162,7 @@ module.exports = ({ strapi }) => ({
|
|
|
116
162
|
Object.entries(data)
|
|
117
163
|
.map(async ([relatedUid, relatedStringIds]) =>
|
|
118
164
|
strapi.db.query(relatedUid).findMany({
|
|
119
|
-
where: { id: Array.from(new Set(relatedStringIds)) }
|
|
165
|
+
where: { id: Array.from(new Set(relatedStringIds)) },
|
|
120
166
|
}).then(relatedEntities => relatedEntities.map(_ =>
|
|
121
167
|
({
|
|
122
168
|
..._,
|
|
@@ -172,8 +218,20 @@ module.exports = ({ strapi }) => ({
|
|
|
172
218
|
return user ? !isNil(user?.id) : true;
|
|
173
219
|
},
|
|
174
220
|
|
|
175
|
-
|
|
176
|
-
const
|
|
221
|
+
async parseRelationString(relation) {
|
|
222
|
+
const [ uid, relatedStringId ] = getRelatedGroups(relation);
|
|
223
|
+
const parsedRelatedId = parseInt(relatedStringId);
|
|
224
|
+
const relatedId = isNumber(parsedRelatedId) ? parsedRelatedId : relatedStringId;
|
|
225
|
+
|
|
226
|
+
const enabledCollections = await this.getConfig(CONFIG_PARAMS.ENABLED_COLLECTIONS, []);
|
|
227
|
+
if (!enabledCollections.includes(uid)) {
|
|
228
|
+
throw new PluginError(403, `Action not allowed for collection '${uid}'. Use one of: ${ enabledCollections.join(', ') }`);
|
|
229
|
+
}
|
|
230
|
+
return [ uid, relatedId];
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
async checkBadWords(content) {
|
|
234
|
+
const config = await this.getConfig(CONFIG_PARAMS.BAD_WORDS, true);
|
|
177
235
|
if (config) {
|
|
178
236
|
const filter = new BadWordsFilter(isObject(config) ? config : undefined);
|
|
179
237
|
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",
|
|
@@ -8,9 +16,11 @@ const REGEX = {
|
|
|
8
16
|
uid: /^(?<type>[a-z0-9-]+)\:{2}(?<api>[a-z0-9-]+)\.{1}(?<contentType>[a-z0-9-]+)$/i,
|
|
9
17
|
relatedUid: /^(?<uid>[a-z0-9-]+\:{2}[a-z0-9-]+\.[a-z0-9-]+)\:{1}(?<id>[a-z0-9-]+)$/i,
|
|
10
18
|
email: /\S+@\S+\.\S+/,
|
|
19
|
+
sorting: /^(?<path>[a-z0-9-_\:\.]+)\:+(asc|desc)$/i
|
|
11
20
|
};
|
|
12
21
|
|
|
13
22
|
module.exports = {
|
|
23
|
+
CONFIG_PARAMS,
|
|
14
24
|
APPROVAL_STATUS,
|
|
15
25
|
REGEX,
|
|
16
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']`);
|
package/.circleci/config.yml
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
version: 2.1
|
|
2
|
-
orbs:
|
|
3
|
-
codecov: codecov/codecov@1.1.1
|
|
4
|
-
jobs:
|
|
5
|
-
build-and-test:
|
|
6
|
-
environment:
|
|
7
|
-
CODECOV_TOKEN: 02e2d4e8-88c4-4e5d-99da-a9a2888634c6
|
|
8
|
-
docker:
|
|
9
|
-
- image: circleci/node:14.17.3
|
|
10
|
-
working_directory: ~/repo
|
|
11
|
-
steps:
|
|
12
|
-
- checkout
|
|
13
|
-
- run:
|
|
14
|
-
name: set GIT name and email
|
|
15
|
-
command: |
|
|
16
|
-
git config --global -l
|
|
17
|
-
git config --global user.email "$GIT_COMMITTER_EMAIL"
|
|
18
|
-
git config --global user.name "$GIT_COMMITTER_NAME"
|
|
19
|
-
git config --global -l
|
|
20
|
-
- restore_cache:
|
|
21
|
-
keys:
|
|
22
|
-
# when lock file changes, use increasingly general patterns to restore cache
|
|
23
|
-
- strapi-plugin-comments-v2-{{ checksum "yarn.lock" }}
|
|
24
|
-
- strapi-plugin-comments-
|
|
25
|
-
- run:
|
|
26
|
-
name: Echo versions
|
|
27
|
-
command: |
|
|
28
|
-
node --version
|
|
29
|
-
yarn --version
|
|
30
|
-
- run:
|
|
31
|
-
name: Install
|
|
32
|
-
command: |
|
|
33
|
-
yarn install
|
|
34
|
-
- run:
|
|
35
|
-
name: Test
|
|
36
|
-
command: |
|
|
37
|
-
yarn test:unit
|
|
38
|
-
- codecov/upload:
|
|
39
|
-
flags: unittest
|
|
40
|
-
file:
|
|
41
|
-
- save_cache:
|
|
42
|
-
paths:
|
|
43
|
-
- ./node_modules
|
|
44
|
-
key: strapi-plugin-comments-v2-{{ checksum "yarn.lock" }}
|
|
45
|
-
workflows:
|
|
46
|
-
build-and-test:
|
|
47
|
-
jobs:
|
|
48
|
-
- build-and-test
|
package/.eslintrc
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"parserOptions": {
|
|
3
|
-
"jsx": true,
|
|
4
|
-
"useJSXTextNode": true,
|
|
5
|
-
"sourceType": "module"
|
|
6
|
-
},
|
|
7
|
-
"plugins": ["react-hooks", "import"],
|
|
8
|
-
"rules": {
|
|
9
|
-
"no-console": "warn",
|
|
10
|
-
"curly": "warn",
|
|
11
|
-
"prefer-template": "warn",
|
|
12
|
-
|
|
13
|
-
"react-hooks/rules-of-hooks": "error",
|
|
14
|
-
"react-hooks/exhaustive-deps": "error",
|
|
15
|
-
|
|
16
|
-
"import/no-default-export": "warn",
|
|
17
|
-
"import/newline-after-import": "warn",
|
|
18
|
-
"import/order": [
|
|
19
|
-
"warn",
|
|
20
|
-
{
|
|
21
|
-
"newlines-between": "always",
|
|
22
|
-
"alphabetize": { "order": "asc" },
|
|
23
|
-
"pathGroupsExcludedImportTypes": ["builtin"],
|
|
24
|
-
"groups": [
|
|
25
|
-
"builtin",
|
|
26
|
-
"external",
|
|
27
|
-
"internal",
|
|
28
|
-
"parent",
|
|
29
|
-
"sibling",
|
|
30
|
-
"index"
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
]
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
## Ticket
|
|
2
|
-
|
|
3
|
-
https://github.com/VirtusLab/strapi-plugin-comments/issues/-<your-ticket-#>
|
|
4
|
-
|
|
5
|
-
## Summary
|
|
6
|
-
|
|
7
|
-
What does this PR do/solve?
|
|
8
|
-
|
|
9
|
-
> PRs should be as small as they need to be, if this section starts becoming a novel you should _seriously_ consider breaking it up into multiple PRs
|
|
10
|
-
|
|
11
|
-
## Test Plan
|
|
12
|
-
|
|
13
|
-
How are you testing the work you're submitting?
|
package/.nvmrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
v14.17.3
|
package/codecov.yml
DELETED
package/public/assets/logo.png
DELETED
|
Binary file
|
|
Binary file
|