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.
Files changed (41) hide show
  1. package/.husky/pre-commit +4 -0
  2. package/README.md +31 -8
  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 +11 -2
  22. package/server/controllers/utils/__tests__/parsers.test.js +94 -0
  23. package/server/controllers/utils/parsers.js +15 -13
  24. package/server/graphql/index.js +7 -8
  25. package/server/graphql/queries/findAllFlat.js +1 -1
  26. package/server/graphql/resolvers-config.js +2 -4
  27. package/server/graphql/types/index.js +3 -0
  28. package/server/graphql/types/report-reason.js +10 -10
  29. package/server/graphql/types/response-find-all.js +7 -0
  30. package/server/graphql/types/response-meta.js +6 -0
  31. package/server/graphql/types/response-pagination.js +11 -0
  32. package/server/register.js +1 -3
  33. package/server/routes/admin.js +32 -0
  34. package/server/services/__tests__/common.test.js +95 -14
  35. package/server/services/admin.js +57 -6
  36. package/server/services/client.js +14 -8
  37. package/server/services/common.js +92 -15
  38. package/server/services/utils/__tests__/functions.test.js +12 -1
  39. package/server/services/utils/functions.js +9 -1
  40. package/server/utils/constants.js +9 -0
  41. package/strapi-server.js +12 -5
@@ -10,6 +10,48 @@ module.exports = {
10
10
  return getPluginService(name);
11
11
  },
12
12
 
13
+ async config(ctx) {
14
+ try {
15
+ return await this.getService().config();
16
+ } catch (e) {
17
+ throwError(ctx, e);
18
+ }
19
+ },
20
+
21
+ async settingsConfig(ctx) {
22
+ try {
23
+ return this.getService().config(true);
24
+ } catch (e) {
25
+ throwError(ctx, e);
26
+ }
27
+ },
28
+
29
+ async settingsUpdateConfig(ctx) {
30
+ try {
31
+ const { request: { body } } = ctx;
32
+ return this.getService().updateConfig(body);
33
+ } catch (e) {
34
+ throwError(ctx, e);
35
+ }
36
+ },
37
+
38
+ async settingsRestoreConfig(ctx) {
39
+ try {
40
+ return this.getService().restoreConfig();
41
+ } catch (e) {
42
+ throwError(ctx, e);
43
+ }
44
+ },
45
+
46
+ async settingsRestart(ctx) {
47
+ try {
48
+ await this.getService().restart();
49
+ return ctx.send({ status: 200 });
50
+ } catch (e) {
51
+ throwError(ctx, e);
52
+ }
53
+ },
54
+
13
55
  async findAll(ctx) {
14
56
  return this.getService()
15
57
  .findAll(ctx.query);
@@ -101,14 +143,6 @@ module.exports = {
101
143
  }
102
144
  },
103
145
 
104
- async config(ctx) {
105
- try {
106
- return await this.getService().config();
107
- } catch (e) {
108
- throwError(ctx, e);
109
- }
110
- },
111
-
112
146
  getContentsTypes() {
113
147
  return Object.entries(strapi.contentTypes)
114
148
  .filter(([, contentType]) => (
@@ -13,9 +13,15 @@ module.exports = {
13
13
  async findAllFlat(ctx) {
14
14
  const { params = {}, query, sort, pagination } = ctx;
15
15
  const { relation } = parseParams(params);
16
+
17
+ const { sort: querySort, pagination: queryPagination, ...filterQuery } = query || {};
18
+
19
+
20
+ console.log(filterQuery);
21
+
16
22
  try {
17
23
  return this.getService('common')
18
- .findAllFlat(flatInput(relation, query, sort, pagination));
24
+ .findAllFlat(flatInput(relation, filterQuery, sort || querySort, pagination || queryPagination));
19
25
  } catch (e) {
20
26
  throwError(ctx, e);
21
27
  }
@@ -24,10 +30,13 @@ module.exports = {
24
30
  async findAllInHierarchy(ctx) {
25
31
  const { params = {}, query, sort } = ctx;
26
32
  const { relation } = parseParams(params);
33
+
34
+ const { sort: querySort, ...filterQuery } = query || {};
35
+
27
36
  try {
28
37
  return await this.getService('common')
29
38
  .findAllInHierarchy({
30
- ...flatInput(relation, query, sort),
39
+ ...flatInput(relation, filterQuery, sort || querySort),
31
40
  dropBlockedThreads: true,
32
41
  });
33
42
  } catch (e) {
@@ -0,0 +1,94 @@
1
+ const { flatInput } = require('../parsers');
2
+
3
+ jest.mock
4
+
5
+ describe('Test Comments controller parsers utils', () => {
6
+
7
+ describe('Flat Input properties', () => {
8
+ const relation = 'api::test.relation';
9
+ const filters = {
10
+ content: {
11
+ '$eq': 'Test'
12
+ }
13
+ };
14
+ const sort = {
15
+ createdAt: 'desc'
16
+ };
17
+ const pagination = {
18
+ page: 3,
19
+ pageSize: 5,
20
+ };
21
+
22
+ test('Should contain default property \'populate\'', () => {
23
+ expect(flatInput()).toHaveProperty(['populate', 'threadOf', 'populate', 'authorUser'], true);
24
+ });
25
+
26
+ test('Should assign relation to \'query\'', () => {
27
+ expect(flatInput(relation)).toHaveProperty(['query', 'related'], relation);
28
+ });
29
+
30
+ test('Should assign filters to \'query\'', () => {
31
+ const result = flatInput(relation, filters);
32
+ expect(result).toHaveProperty(['query', 'content', '$eq'], filters.content['$eq']);
33
+ });
34
+
35
+ test('Should assign not allow to overwrite \'removed\' in $or operator', () => {
36
+ const overwrittenOrOperator = {
37
+ $or: [{ removed: true }, { removed: false }, { removed: null }]
38
+ };
39
+ const result = flatInput(relation, overwrittenOrOperator);
40
+ expect(result).toHaveProperty(['query', '$or', 0, 'removed', '$null'], true);
41
+ expect(result).toHaveProperty(['query', '$or', 1, 'removed'], false);
42
+ });
43
+
44
+ test('Should assign sort', () => {
45
+ const result = flatInput(relation, filters, sort);
46
+ expect(result).toHaveProperty(['sort', 'createdAt'], sort.createdAt);
47
+ });
48
+
49
+ test('Should assign pagination', () => {
50
+ const result = flatInput(relation, filters, sort, pagination);
51
+ expect(result).toHaveProperty(['pagination', 'page'], pagination.page);
52
+ expect(result).toHaveProperty(['pagination', 'pageSize'], pagination.pageSize);
53
+ });
54
+
55
+ test('Should build complex output', () => {
56
+ const day = 24 * 60 * 60 * 1000;
57
+ const now = Date.now().toString();
58
+ const oneDayBefore = new Date(Date.now().valueOf() - day).toString();
59
+ const complexFilters = {
60
+ '$or': [{
61
+ createdAt: {
62
+ '$lte': oneDayBefore
63
+ }
64
+ }, {
65
+ createdAt: {
66
+ '$eq': now
67
+ }
68
+ }],
69
+ content: {
70
+ '$contains': 'Test'
71
+ }
72
+ };
73
+
74
+ const complexSort = [{
75
+ createdAt: 'desc'
76
+ }, {
77
+ title: 'asc'
78
+ }];
79
+
80
+ const result = flatInput(relation, complexFilters, complexSort, pagination);
81
+
82
+ expect(result).toHaveProperty(['query', 'related'], relation);
83
+ expect(result).toHaveProperty(['query', '$or', 0, 'createdAt', '$lte'], oneDayBefore);
84
+ expect(result).toHaveProperty(['query', '$or', 1, 'createdAt', '$eq'], now);
85
+ expect(result).toHaveProperty(['query', '$or', 2, 'removed', '$null'], true);
86
+ expect(result).toHaveProperty(['query', '$or', 3, 'removed'], false);
87
+ expect(result).toHaveProperty(['query', 'content', '$contains'], complexFilters.content['$contains']);
88
+ expect(result).toHaveProperty(['sort', 0, 'createdAt'], complexSort[0].createdAt);
89
+ expect(result).toHaveProperty(['sort', 1, 'title'], complexSort[1].title);
90
+ expect(result).toHaveProperty(['pagination', 'page'], pagination.page);
91
+ expect(result).toHaveProperty(['pagination', 'pageSize'], pagination.pageSize);
92
+ });
93
+ });
94
+ });
@@ -2,20 +2,22 @@
2
2
 
3
3
  module.exports = {
4
4
  flatInput(relation, query, sort, pagination) {
5
- return {
6
- query: {
7
- ...query,
8
- $or: [{ removed: { $null: true } }, { removed: false }],
9
- related: relation,
5
+ const filters = query?.filters || query;
6
+ const orOperator = (filters?.$or || []).filter(_ => !Object.keys(_).includes('removed'));
7
+ return {
8
+ query: {
9
+ ...filters,
10
+ $or: [...orOperator, { removed: { $null: true } }, { removed: false }],
11
+ related: relation,
12
+ },
13
+ populate: {
14
+ threadOf: {
15
+ populate: { authorUser: true },
10
16
  },
11
- populate: {
12
- threadOf: {
13
- populate: { authorUser: true },
14
- },
15
- },
16
- pagination,
17
- sort,
18
- };
17
+ },
18
+ pagination,
19
+ sort,
20
+ };
19
21
  },
20
22
 
21
23
  };
@@ -6,17 +6,16 @@ const getMutations = require('./mutations');
6
6
  const getResolversConfig = require('./resolvers-config');
7
7
  const { getModelUid } = require('../services/utils/functions');
8
8
 
9
- module.exports = () => {
9
+ module.exports = async ({ strapi, config}) => {
10
10
  const extensionService = strapi.plugin('graphql').service('extension');
11
-
12
11
  extensionService.shadowCRUD(getModelUid('comment')).disable();
13
12
  extensionService.shadowCRUD(getModelUid('comment-report')).disable();
14
-
15
- extensionService.use(({ nexus }) => {
16
- const types = getTypes({ strapi, nexus });
17
- const queries = getQueries({ strapi, nexus });
18
- const mutations = getMutations({ strapi, nexus });
19
- const resolversConfig = getResolversConfig({ strapi });
13
+
14
+ extensionService.use(({ strapi, nexus }) => {
15
+ const types = getTypes({ strapi, nexus, config });
16
+ const queries = getQueries({ strapi, nexus, config });
17
+ const mutations = getMutations({ strapi, nexus, config });
18
+ const resolversConfig = getResolversConfig({ strapi, config });
20
19
 
21
20
  return {
22
21
  types: [types, queries, mutations],
@@ -10,7 +10,7 @@ module.exports = ({ strapi, nexus }) => {
10
10
  const { args } = getService('internals');
11
11
 
12
12
  return {
13
- type: nonNull(list('CommentSingle')),
13
+ type: 'ResponseFindAll',
14
14
  args: {
15
15
  relation: nonNull(stringArg()),
16
16
  filters: getPluginService('gql').buildContentTypeFilters(contentType),
@@ -1,9 +1,7 @@
1
1
  'use strict'
2
2
 
3
- const { getPluginService } = require("../utils/functions");
4
-
5
- module.exports = () => {
6
- const auth = getPluginService('common').getConfig('gql.auth', false);
3
+ module.exports = ({ config }) => {
4
+ const { gql: { auth = false } = {}} = config;
7
5
  return {
8
6
  'Query.findAllFlat': { auth },
9
7
  'Query.findAllInHierarchy': { auth },
@@ -11,6 +11,9 @@ const typesFactories = [
11
11
  require('./report'),
12
12
  require('./report-reason'),
13
13
  require('./create-report'),
14
+ require('./response-pagination'),
15
+ require('./response-meta'),
16
+ require('./response-find-all'),
14
17
  ];
15
18
 
16
19
  module.exports = context => typesFactories.map(factory => factory(context));
@@ -1,10 +1,10 @@
1
- const { getPluginService } = require("../../utils/functions");
2
-
3
-
4
- module.exports = ({ nexus }) => nexus.enumType({
5
- name: "ReportReason",
6
- description: 'Reason of abuse report',
7
- members: {
8
- ...getPluginService('common').getConfig('reportReasons', {})
9
- },
10
- })
1
+ module.exports = ({ nexus, config }) => {
2
+ const { reportReasons } = config;
3
+ return nexus.enumType({
4
+ name: "ReportReason",
5
+ description: 'Reason of abuse report',
6
+ members: {
7
+ ...reportReasons
8
+ },
9
+ })
10
+ };
@@ -0,0 +1,7 @@
1
+ module.exports = ({ nexus }) => nexus.objectType({
2
+ name: "ResponseFindAll",
3
+ definition(t) {
4
+ t.list.field("data", { type: 'CommentSingle' })
5
+ t.field("meta", { type: 'ResponseMeta' })
6
+ }
7
+ })
@@ -0,0 +1,6 @@
1
+ module.exports = ({ nexus }) => nexus.objectType({
2
+ name: "ResponseMeta",
3
+ definition(t) {
4
+ t.field("pagination", { type: 'ResponsePagination' })
5
+ }
6
+ })
@@ -0,0 +1,11 @@
1
+ module.exports = ({ nexus }) => nexus.objectType({
2
+ name: "ResponsePagination",
3
+ definition(t) {
4
+ t.int('page')
5
+ t.int('pageSize')
6
+ t.int('pageCount')
7
+ t.int('total')
8
+ t.int('start')
9
+ t.int('limit')
10
+ }
11
+ })
@@ -1,7 +1,5 @@
1
1
  'use strict';
2
2
 
3
3
  module.exports = ({ strapi }) => {
4
- if (strapi.plugin('graphql')) {
5
- require('./graphql')({ strapi });
6
- }
4
+ // register phase
7
5
  };
@@ -89,4 +89,36 @@ module.exports = [
89
89
  policies: [],
90
90
  },
91
91
  },
92
+ {
93
+ method: 'GET',
94
+ path: '/settings/config',
95
+ handler: 'admin.settingsConfig',
96
+ config: {
97
+ policies: [],
98
+ },
99
+ },
100
+ {
101
+ method: 'PUT',
102
+ path: '/settings/config',
103
+ handler: 'admin.settingsUpdateConfig',
104
+ config: {
105
+ policies: [],
106
+ },
107
+ },
108
+ {
109
+ method: 'DELETE',
110
+ path: '/settings/config',
111
+ handler: 'admin.settingsRestoreConfig',
112
+ config: {
113
+ policies: [],
114
+ },
115
+ },
116
+ {
117
+ method: 'GET',
118
+ path: '/settings/restart',
119
+ handler: 'admin.settingsRestart',
120
+ config: {
121
+ policies: [],
122
+ },
123
+ },
92
124
  ];
@@ -1,3 +1,4 @@
1
+ const { CONFIG_PARAMS } = require('../../utils/constants');
1
2
  const PluginError = require('../../utils/error');
2
3
  const { getPluginService } = require('../../utils/functions');
3
4
 
@@ -16,18 +17,48 @@ afterEach(() => {
16
17
  })
17
18
 
18
19
  describe('Test Comments service functions utils', () => {
20
+ describe('Get plugin store', () => {
21
+ beforeEach(() => setup());
19
22
 
20
- describe('Get plugin config', () => {
21
- beforeEach(() => setup({
22
- test: 'sample',
23
- }));
23
+ test('Should return store', async () => {
24
+ const pluginStore = await getPluginService('common').getPluginStore();
25
+ expect(pluginStore).toHaveProperty('get');
26
+ expect(pluginStore).toHaveProperty('set');
27
+ expect(pluginStore).toHaveProperty('delete');
28
+ });
29
+ });
24
30
 
25
- test('Should return config value', () => {
26
- expect(getPluginService('common').getConfig('test')).toEqual('sample');
31
+ describe('Get plugin config', () => {
32
+ describe('Local config', () => {
33
+ beforeEach(() => setup({
34
+ test: 'sample',
35
+ }));
36
+
37
+ test('Should return config value', async () => {
38
+ const result = await getPluginService('common').getConfig('test');
39
+ expect(result).toEqual('sample');
40
+ });
41
+
42
+ test('Should return default config prop value if not set', async () => {
43
+ const result = await getPluginService('common').getConfig('another', 'defaultValue');
44
+ expect(result).toEqual('defaultValue');
45
+ });
27
46
  });
28
47
 
29
- test('Should return default config prop value if not set', () => {
30
- expect(getPluginService('common').getConfig('another', 'defaultValue')).toEqual('defaultValue');
48
+ describe('Store config', () => {
49
+ beforeEach(() => setup({
50
+ test: 'sample',
51
+ }, true));
52
+
53
+ test('Should return config value', async () => {
54
+ const result = await getPluginService('common').getConfig('test');
55
+ expect(result).toEqual('sample');
56
+ });
57
+
58
+ test('Should return default config prop value if not set', async () => {
59
+ const result = await getPluginService('common').getConfig('another', 'defaultValue');
60
+ expect(result).toEqual('defaultValue');
61
+ });
31
62
  });
32
63
  });
33
64
 
@@ -131,12 +162,12 @@ describe('Test Comments service functions utils', () => {
131
162
 
132
163
  const text = 'Lorem ipsum dolor sit fuck amet';
133
164
 
134
- test('Should find bad words usage and throw error PluginError', () => {
165
+ test('Should find bad words usage and throw error PluginError', async () => {
135
166
  expect.assertions(7);
136
167
  try {
137
- getPluginService('common').checkBadWords(text);
168
+ await getPluginService('common').checkBadWords(text);
138
169
  } catch (e) {
139
- expect(getPluginService('common').getConfig('badWords')).toEqual(undefined);
170
+ expect(await getPluginService('common').getConfig('badWords')).toEqual(undefined);
140
171
  expect(e).toBeInstanceOf(PluginError);
141
172
  expect(e).toHaveProperty('status', 400);
142
173
  expect(e).toHaveProperty('name', 'Strapi:Plugin:Comments');
@@ -157,9 +188,59 @@ describe('Test Comments service functions utils', () => {
157
188
 
158
189
  const text = 'Lorem ipsum dolor sit fuck amet';
159
190
 
160
- test('Should skip bad words filtering because of configuration change', () => {
161
- expect(getPluginService('common').getConfig('badWords')).toEqual(false);
162
- expect(getPluginService('common').checkBadWords(text)).toEqual(text);
191
+ test('Should skip bad words filtering because of configuration change', async () => {
192
+ expect(await getPluginService('common').getConfig('badWords')).toEqual(false);
193
+ expect(await getPluginService('common').checkBadWords(text)).toEqual(text);
194
+ });
195
+ });
196
+
197
+ describe('Parse relation string (store config)', () => {
198
+ const [uid, id] = ['api::my-collection.my-content-type', '1'];
199
+ const validCollection = `${uid}:${id}`;
200
+
201
+ const [wrongUid, wrongId] = ['api::wrong.type', '2'];
202
+ const testCollection = `${wrongUid}:${wrongId}`;
203
+
204
+ describe('Store config', () => {
205
+ beforeEach(() => setup({ enabledCollections: [uid] }, true));
206
+
207
+ test('Should pass', async () => {
208
+ expect(await getPluginService('common').parseRelationString(validCollection));
209
+ expect(await getPluginService('common').getConfig(CONFIG_PARAMS.ENABLED_COLLECTIONS)).toContain(uid);
210
+ });
211
+
212
+ test('Should fail with 403', async () => {
213
+
214
+ try {
215
+ await getPluginService('common').parseRelationString(testCollection);
216
+ } catch(e) {
217
+ expect(await getPluginService('common').getConfig(CONFIG_PARAMS.ENABLED_COLLECTIONS)).toContain(uid);
218
+ expect(await getPluginService('common').getConfig(CONFIG_PARAMS.ENABLED_COLLECTIONS)).not.toContain(wrongUid);
219
+ expect(e).toBeInstanceOf(PluginError);
220
+ expect(e).toHaveProperty('status', 403);
221
+ }
222
+ });
223
+ });
224
+
225
+ describe('Local config', () => {
226
+ beforeEach(() => setup({ enabledCollections: [uid] }));
227
+
228
+ test('Should pass', async () => {
229
+ expect(await getPluginService('common').parseRelationString(validCollection));
230
+ expect(await getPluginService('common').getConfig(CONFIG_PARAMS.ENABLED_COLLECTIONS)).toContain(uid);
231
+ });
232
+
233
+ test('Should fail with 403', async () => {
234
+
235
+ try {
236
+ await getPluginService('common').parseRelationString(testCollection);
237
+ } catch(e) {
238
+ expect(await getPluginService('common').getConfig(CONFIG_PARAMS.ENABLED_COLLECTIONS)).toContain(uid);
239
+ expect(await getPluginService('common').getConfig(CONFIG_PARAMS.ENABLED_COLLECTIONS)).not.toContain(wrongUid);
240
+ expect(e).toBeInstanceOf(PluginError);
241
+ expect(e).toHaveProperty('status', 403);
242
+ }
243
+ });
163
244
  });
164
245
  });
165
246
  });
@@ -21,17 +21,68 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
21
21
  },
22
22
 
23
23
  // Config
24
- async config() {
25
- const entryLabel = this.getCommonService().getConfig('entryLabel');
26
- const approvalFlow = this.getCommonService().getConfig('approvalFlow');
27
- return {
28
- entryLabel,
29
- approvalFlow,
24
+ async config(viaSettingsPage = false) {
25
+ const pluginStore = await this.getCommonService().getPluginStore();
26
+ const config = await pluginStore.get({ key: 'config' });
27
+ const additionalConfiguration = {
30
28
  regex: Object.keys(REGEX).reduce((prev, curr) => ({
31
29
  ...prev,
32
30
  [curr]: REGEX[curr].toString(),
33
31
  }), {}),
34
32
  };
33
+
34
+ const isGQLPluginEnabled = !isNil(strapi.plugin('graphql'));
35
+
36
+ if (config) {
37
+ return {
38
+ ...config,
39
+ ...additionalConfiguration,
40
+ isGQLPluginEnabled: viaSettingsPage ? isGQLPluginEnabled : undefined,
41
+ };
42
+ }
43
+
44
+ const entryLabel = this.getCommonService().getLocalConfig('entryLabel');
45
+ const approvalFlow = this.getCommonService().getLocalConfig('approvalFlow');
46
+ const reportReasons = this.getCommonService().getLocalConfig('reportReasons');
47
+ const result = {
48
+ entryLabel,
49
+ approvalFlow,
50
+ reportReasons,
51
+ ...additionalConfiguration,
52
+ };
53
+
54
+ if (viaSettingsPage) {
55
+ const enabledCollections = this.getCommonService().getLocalConfig('enabledCollections');
56
+ const moderatorRoles = this.getCommonService().getLocalConfig('moderatorRoles');
57
+ return {
58
+ ...result,
59
+ enabledCollections,
60
+ moderatorRoles,
61
+ isGQLPluginEnabled,
62
+ };
63
+ }
64
+
65
+ return result;
66
+ },
67
+
68
+ async updateConfig(body) {
69
+ const pluginStore = await this.getCommonService().getPluginStore();
70
+
71
+ await pluginStore.set({ key: 'config', value: body });
72
+
73
+ return this.config();
74
+ },
75
+
76
+ async restoreConfig() {
77
+ const pluginStore = await this.getCommonService().getPluginStore();
78
+
79
+ await pluginStore.delete({key: 'config'})
80
+
81
+ return this.config();
82
+ },
83
+
84
+ async restart() {
85
+ setImmediate(() => strapi.reload());
35
86
  },
36
87
 
37
88
  // Find all comments