strapi-plugin-comments 2.0.1 → 2.0.5

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.
Files changed (45) hide show
  1. package/.husky/pre-commit +4 -0
  2. package/README.md +34 -6
  3. package/__mocks__/initSetup.js +31 -2
  4. package/admin/src/components/FormSwitch/index.js +18 -0
  5. package/admin/src/components/FormSwitch/styles.js +8 -0
  6. package/admin/src/hooks/useConfig.js +49 -0
  7. package/admin/src/index.js +31 -2
  8. package/admin/src/pages/Details/index.js +5 -2
  9. package/admin/src/pages/Settings/components/RestartAlert/styles.js +8 -0
  10. package/admin/src/pages/Settings/index.js +392 -0
  11. package/admin/src/pages/Settings/utils/api.js +60 -0
  12. package/admin/src/permissions.js +3 -0
  13. package/admin/src/translations/en.json +46 -1
  14. package/admin/src/utils/handleAPIError.js +3 -2
  15. package/admin/src/utils/index.js +2 -1
  16. package/admin/src/utils/parseRegExp.js +10 -0
  17. package/package.json +10 -7
  18. package/server/bootstrap.js +24 -0
  19. package/server/config/index.js +1 -0
  20. package/server/controllers/admin.js +42 -8
  21. package/server/controllers/client.js +13 -4
  22. package/server/controllers/utils/__tests__/parsers.test.js +94 -0
  23. package/server/controllers/utils/parsers.js +16 -12
  24. package/server/graphql/index.js +7 -8
  25. package/server/graphql/queries/findAllFlat.js +4 -2
  26. package/server/graphql/queries/findAllInHierarchy.js +3 -3
  27. package/server/graphql/resolvers-config.js +2 -4
  28. package/server/graphql/types/report-reason.js +10 -10
  29. package/server/register.js +1 -3
  30. package/server/routes/admin.js +32 -0
  31. package/server/services/__tests__/common.test.js +95 -14
  32. package/server/services/admin.js +57 -6
  33. package/server/services/client.js +14 -8
  34. package/server/services/common.js +69 -11
  35. package/server/services/utils/__tests__/functions.test.js +12 -1
  36. package/server/services/utils/functions.js +9 -1
  37. package/server/utils/constants.js +10 -0
  38. package/strapi-server.js +12 -5
  39. package/.circleci/config.yml +0 -48
  40. package/.eslintrc +0 -35
  41. package/.github/pull_request_template.md +0 -13
  42. package/.nvmrc +0 -1
  43. package/codecov.yml +0 -4
  44. package/public/assets/logo.png +0 -0
  45. 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
- let queryProp = prop;
22
- if (prop && isArray(prop)) {
23
- queryProp = prop.join('.');
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({ query = {}, populate = {} }, relatedEntity = null) {
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, 'Not found');
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
- checkBadWords(content) {
176
- const config = this.getConfig('badWords', true);
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.moderatorRoles
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.approvalFlow
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.entryLabel)
40
+ const entryLabelKeysValid = Object.keys(config[CONFIG_PARAMS.ENTRY_LABEL])
34
41
  .filter(_ => _ !== '*')
35
- .filter(_ => !(REGEX.uid.test(_) && isArray(config.entryLabel[_]) && !isEmpty(config.entryLabel[_]))).length === 0;
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']`);
@@ -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
@@ -1,4 +0,0 @@
1
- comment:
2
- branches:
3
- - master
4
- - feat/strapi-v4
Binary file
Binary file