strapi-plugin-comments 1.0.2-beta.1 → 2.0.0-beta.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.
Files changed (149) hide show
  1. package/.circleci/config.yml +2 -2
  2. package/README.md +71 -95
  3. package/admin/src/components/ConfirmationDialog/index.js +56 -0
  4. package/admin/src/components/DiscussionStatus/index.js +33 -0
  5. package/admin/src/components/DiscussionThread/index.js +58 -0
  6. package/admin/src/components/DiscussionThreadItem/index.js +87 -0
  7. package/admin/src/components/DiscussionThreadItem/styles.js +87 -0
  8. package/admin/src/components/DiscussionThreadItemActions/index.js +176 -0
  9. package/admin/src/components/DiscussionThreadItemActions/styles.js +22 -0
  10. package/admin/src/components/DiscussionThreadItemMeta/index.js +31 -0
  11. package/admin/src/components/DiscussionThreadItemWarnings/index.js +137 -0
  12. package/admin/src/components/DiscussionThreadItemWarnings/styles.js +7 -0
  13. package/admin/src/components/EntityDetails/index.js +53 -0
  14. package/admin/src/{containers → components}/Initializer/index.js +4 -4
  15. package/admin/src/components/LoadingIndicatorOverlay/index.js +15 -0
  16. package/admin/src/components/LoadingIndicatorOverlay/styles.js +20 -0
  17. package/admin/src/components/Nav/index.js +45 -0
  18. package/admin/src/components/PluginIcon/index.js +12 -0
  19. package/admin/src/components/ReportsReviewModal/index.js +45 -0
  20. package/admin/src/components/ReportsReviewTable/index.js +142 -0
  21. package/admin/src/components/TableFilters/index.js +53 -0
  22. package/admin/src/components/TablePagination/index.js +35 -0
  23. package/admin/src/components/icons/comments.js +9 -0
  24. package/admin/src/index.js +58 -42
  25. package/admin/src/pages/App/index.js +60 -0
  26. package/admin/src/pages/App/reducer/actions.js +8 -0
  27. package/admin/src/pages/App/reducer/constants.js +4 -0
  28. package/admin/src/pages/App/reducer/index.js +27 -0
  29. package/admin/src/pages/App/reducer/init.js +7 -0
  30. package/admin/src/pages/App/reducer/selectors.js +35 -0
  31. package/admin/src/pages/App/utils/api.js +20 -0
  32. package/admin/src/pages/ComingSoonPage/index.js +25 -0
  33. package/admin/src/pages/Details/index.js +109 -0
  34. package/admin/src/pages/Details/utils/api.js +41 -0
  35. package/admin/src/pages/Discover/index.js +203 -0
  36. package/admin/src/pages/Discover/utils/api.js +20 -0
  37. package/admin/src/pages/Discover/utils/filtersSchema.js +9 -0
  38. package/admin/src/permissions.js +13 -33
  39. package/admin/src/pluginId.js +2 -4
  40. package/admin/src/reducers.js +8 -0
  41. package/admin/src/translations/en.json +72 -33
  42. package/admin/src/translations/fr.json +1 -0
  43. package/admin/src/utils/axiosInstance.js +35 -0
  44. package/admin/src/utils/constants.js +28 -0
  45. package/admin/src/utils/getApiUrl.js +5 -0
  46. package/admin/src/utils/getMessage.js +14 -0
  47. package/admin/src/utils/getRelatedGroups.js +5 -0
  48. package/admin/src/utils/getUrl.js +5 -0
  49. package/admin/src/utils/handleAPIError.js +15 -0
  50. package/admin/src/utils/index.js +6 -0
  51. package/codecov.yml +2 -1
  52. package/content-types/comment.js +97 -0
  53. package/content-types/index.js +11 -0
  54. package/content-types/report.js +54 -0
  55. package/package.json +11 -25
  56. package/server/bootstrap.js +5 -0
  57. package/server/config/index.js +10 -0
  58. package/server/controllers/admin.js +134 -0
  59. package/server/controllers/client.js +109 -0
  60. package/server/controllers/index.js +9 -0
  61. package/{controllers → server/controllers}/utils/__tests__/functions.test.js +0 -0
  62. package/{controllers → server/controllers}/utils/functions.js +1 -1
  63. package/server/destroy.js +5 -0
  64. package/server/index.js +22 -0
  65. package/server/register.js +5 -0
  66. package/server/routes/admin.js +92 -0
  67. package/server/routes/client.js +52 -0
  68. package/server/routes/index.js +15 -0
  69. package/server/services/admin.js +186 -0
  70. package/server/services/client.js +227 -0
  71. package/server/services/common.js +191 -0
  72. package/server/services/index.js +11 -0
  73. package/{services → server/services}/utils/__tests__/functions.test.js +7 -9
  74. package/{services → server/services}/utils/functions.js +19 -8
  75. package/server/utils/constants.js +16 -0
  76. package/{services → server}/utils/error.js +0 -0
  77. package/server/utils/functions.js +19 -0
  78. package/strapi-admin.js +3 -0
  79. package/strapi-server.js +9 -0
  80. package/admin/src/assets/images/icon-cross-blue.svg +0 -1
  81. package/admin/src/assets/images/icon_remove.svg +0 -19
  82. package/admin/src/assets/images/logo.svg +0 -5
  83. package/admin/src/components/AbuseReportsPopUp/ItemPreviewContainer.js +0 -8
  84. package/admin/src/components/AbuseReportsPopUp/ModalFooter.js +0 -31
  85. package/admin/src/components/AbuseReportsPopUp/TableContainer.js +0 -63
  86. package/admin/src/components/AbuseReportsPopUp/index.js +0 -148
  87. package/admin/src/components/Container/index.js +0 -7
  88. package/admin/src/components/EmptyView/index.js +0 -25
  89. package/admin/src/components/Filters/index.js +0 -151
  90. package/admin/src/components/Filters/index.styled.js +0 -82
  91. package/admin/src/components/Filters/utils.js +0 -21
  92. package/admin/src/components/Item/CardItem.js +0 -30
  93. package/admin/src/components/Item/CardWrapper.js +0 -10
  94. package/admin/src/components/Item/index.js +0 -92
  95. package/admin/src/components/ItemDetails/CardItem.js +0 -33
  96. package/admin/src/components/ItemDetails/CardLevelCounter.js +0 -46
  97. package/admin/src/components/ItemDetails/CardLevelCounterLink.js +0 -24
  98. package/admin/src/components/ItemDetails/CardLevelWrapper.js +0 -22
  99. package/admin/src/components/ItemDetails/CardWrapper.js +0 -52
  100. package/admin/src/components/ItemDetails/index.js +0 -151
  101. package/admin/src/components/ItemFooter/CardItemAuthor.js +0 -10
  102. package/admin/src/components/ItemFooter/CardItemRelation.js +0 -18
  103. package/admin/src/components/ItemFooter/Wrapper.js +0 -20
  104. package/admin/src/components/ItemFooter/index.js +0 -87
  105. package/admin/src/components/ItemHeader/CardHeaderBlocked.js +0 -24
  106. package/admin/src/components/ItemHeader/CardHeaderIndicator.js +0 -30
  107. package/admin/src/components/ItemHeader/CardHeaderIndicatorBlue.js +0 -11
  108. package/admin/src/components/ItemHeader/CardHeaderIndicatorRed.js +0 -11
  109. package/admin/src/components/ItemHeader/CardHeaderIndicatorsContainer.js +0 -18
  110. package/admin/src/components/ItemHeader/CardHeaderReports.js +0 -40
  111. package/admin/src/components/ItemHeader/Wrapper.js +0 -16
  112. package/admin/src/components/ItemHeader/index.js +0 -80
  113. package/admin/src/components/ItemModeration/Wrapper.js +0 -19
  114. package/admin/src/components/ItemModeration/index.js +0 -47
  115. package/admin/src/components/List/Container.js +0 -20
  116. package/admin/src/components/List/index.js +0 -21
  117. package/admin/src/components/Search/index.js +0 -83
  118. package/admin/src/containers/App/Wrapper.js +0 -14
  119. package/admin/src/containers/App/index.js +0 -47
  120. package/admin/src/containers/DataManagerProvider/actions.js +0 -11
  121. package/admin/src/containers/DataManagerProvider/index.js +0 -280
  122. package/admin/src/containers/DataManagerProvider/init.js +0 -5
  123. package/admin/src/containers/DataManagerProvider/reducer.js +0 -59
  124. package/admin/src/containers/DetailsView/Wrapper.js +0 -18
  125. package/admin/src/containers/DetailsView/index.js +0 -100
  126. package/admin/src/containers/ListView/FadedWrapper.js +0 -42
  127. package/admin/src/containers/ListView/Footer.js +0 -53
  128. package/admin/src/containers/ListView/Wrapper.js +0 -17
  129. package/admin/src/containers/ListView/components.js +0 -138
  130. package/admin/src/containers/ListView/index.js +0 -45
  131. package/admin/src/containers/Panel/actions.js +0 -5
  132. package/admin/src/containers/Panel/index.js +0 -191
  133. package/admin/src/containers/Panel/index.styled.js +0 -6
  134. package/admin/src/containers/Panel/reducer.js +0 -44
  135. package/admin/src/contexts/DataManagerContext.js +0 -5
  136. package/admin/src/hooks/useDataManager.js +0 -6
  137. package/admin/src/lifecycles.js +0 -3
  138. package/admin/src/translations/index.js +0 -7
  139. package/admin/src/utils/getTrad.js +0 -5
  140. package/admin/src/utils/getTradId.js +0 -5
  141. package/config/functions/bootstrap.js +0 -48
  142. package/config/routes.json +0 -116
  143. package/config/schema.graphql.js +0 -270
  144. package/controllers/comments.js +0 -211
  145. package/models/comment.js +0 -8
  146. package/models/comment.settings.json +0 -98
  147. package/models/report.js +0 -8
  148. package/models/report.settings.json +0 -49
  149. package/services/comments.js +0 -486
@@ -0,0 +1,186 @@
1
+ 'use strict';
2
+
3
+ const { getPluginService, parseParams } = require('./../utils/functions');
4
+ const { getAssociationModel } = require('./common');
5
+ const { first, isEmpty, isNil, uniq, get } = require('lodash');
6
+ const PluginError = require('./../utils/error');
7
+ const {
8
+ getModelUid,
9
+ getRelatedGroups,
10
+ filterOurResolvedReports,
11
+ convertContentTypeNameToSlug
12
+ } = require('./utils/functions');
13
+ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
14
+
15
+ /**
16
+ * Comments Plugin - Moderation services
17
+ */
18
+
19
+ module.exports = ({ strapi }) => ({
20
+
21
+ getCommonService() {
22
+ return getPluginService('common');
23
+ },
24
+
25
+ // Config
26
+ async config() {
27
+ const entryLabel = this.getCommonService().getConfig('entryLabel');
28
+ return {
29
+ entryLabel,
30
+ };
31
+ },
32
+
33
+ // Find all comments
34
+ async findAll({ related, entity, ...query }) {
35
+ const { _q, orderBy, pageSize = 10, page = 1, filters, ...rest } = parseParams(query);
36
+
37
+ let params = {
38
+ where: !isEmpty(filters) ? {
39
+ ...filters,
40
+ } : undefined,
41
+ offset: (page - 1)*pageSize,
42
+ limit: pageSize,
43
+ orderBy: orderBy || [{ createdAt: 'desc' }],
44
+ };
45
+ if (_q) {
46
+ params = {
47
+ ...params,
48
+ where: {
49
+ ...params.where,
50
+ content: {
51
+ $contains: _q
52
+ },
53
+ }
54
+ };
55
+ }
56
+
57
+ const entities = await strapi.db.query(getModelUid('comment'))
58
+ .findMany(params, ['authorUser', 'reports']);
59
+ const total = await strapi.db.query(getModelUid('comment'))
60
+ .count({
61
+ where: params.where,
62
+ });
63
+ const relatedEntities = await this.getCommonService().findRelatedEntitiesFor(entities);
64
+ const result = entities
65
+ .map(_ => filterOurResolvedReports(this.getCommonService().sanitizeCommentEntity(_)))
66
+ .map(_ => this.getCommonService().mergeRelatedEntityTo(_, relatedEntities));
67
+
68
+ return {
69
+ result,
70
+ pagination: {
71
+ page: page,
72
+ pageSize: pageSize,
73
+ pageCount: Math.floor(total / pageSize) + 1,
74
+ total,
75
+ },
76
+ };
77
+ },
78
+
79
+ // Find single comment
80
+ async findOneAndThread(id) {
81
+ const entity = await strapi.db.query(getModelUid('comment')).findOne({
82
+ where: {
83
+ id
84
+ },
85
+ populate: ['threadOf', 'threadOf.reports', 'authorUser', 'reports']
86
+ });
87
+
88
+ if (!entity){
89
+ throw new PluginError(404, 'Not found');
90
+ }
91
+
92
+ const [relatedUid, relatedStringId] = getRelatedGroups(entity.related);
93
+ const relatedId = parseInt(relatedStringId, 10);
94
+ const relatedEntity = await strapi.db.query(relatedUid).findOne({
95
+ where: { id: relatedId }
96
+ }).then(_ => ({
97
+ ..._,
98
+ uid: relatedUid,
99
+ }));
100
+
101
+ if (!relatedEntity) {
102
+ throw new PluginError(404, 'Relation not found');
103
+ }
104
+
105
+ const levelThreadId = entity?.threadOf?.id || null;
106
+ const entitiesOnSameLevel = await this.getCommonService()
107
+ .findAllInHierarchy({
108
+ query: {
109
+ threadOf: levelThreadId,
110
+ related: entity.related,
111
+ },
112
+ startingFromId: levelThreadId,
113
+ isAdmin: true
114
+ }, false);
115
+
116
+ const selectedEntity = filterOurResolvedReports(this.getCommonService().sanitizeCommentEntity(entity));
117
+
118
+ return {
119
+ entity: relatedEntity,
120
+ selected: {
121
+ ...selectedEntity,
122
+ threadOf: selectedEntity.threadOf ? filterOurResolvedReports(selectedEntity.threadOf) : null,
123
+ },
124
+ level: entitiesOnSameLevel.map(_ => filterOurResolvedReports(_))
125
+ };
126
+ },
127
+
128
+ // Block / Unblock a comment
129
+ async blockComment(id, forceStatus) {
130
+ const existingEntity = await this.getCommonService().findOne({ id });
131
+ const changedEntity = await strapi.db.query(getModelUid('comment')).update({
132
+ where: { id },
133
+ data: { blocked: !isNil(forceStatus) ? forceStatus : !existingEntity.blocked }
134
+ });
135
+ return this.getCommonService().sanitizeCommentEntity(changedEntity);
136
+ },
137
+
138
+ // Block / Unblock a comment thread
139
+ async blockCommentThread(id, forceStatus) {
140
+ const existingEntity = await this.getCommonService().findOne({ id });
141
+ const changedEntity = await strapi.db.query(getModelUid('comment')).update({
142
+ where: { id },
143
+ data: { blockedThread: !isNil(forceStatus) ? forceStatus : !existingEntity.blockedThread }
144
+ });
145
+ await this.blockNestedThreads(id, changedEntity.blockedThread);
146
+
147
+ return this.getCommonService().sanitizeCommentEntity(changedEntity);
148
+ },
149
+
150
+ // Approve comment
151
+ async approveComment(id) {
152
+ const changedEntity = await strapi.db.query(getModelUid('comment'))
153
+ .update({
154
+ where: { id },
155
+ data: { approvalStatus: APPROVAL_STATUS.APPROVED }
156
+ });
157
+
158
+ return this.getCommonService().sanitizeCommentEntity(changedEntity);
159
+ },
160
+
161
+ async rejectComment(id) {
162
+ const changedEntity = await strapi.db.query(getModelUid('comment'))
163
+ .update({
164
+ where: { id },
165
+ data: { approvalStatus: APPROVAL_STATUS.REJECTED }
166
+ });
167
+
168
+ return this.getCommonService().sanitizeCommentEntity(changedEntity);
169
+ },
170
+
171
+ async blockNestedThreads(id, blockStatus) {
172
+ return await this.getCommonService()
173
+ .modifiedNestedNestedComments(id, 'blockedThread', blockStatus)
174
+ },
175
+
176
+ // Resolve reported abuse for comment
177
+ async resolveAbuseReport(id, commentId) {
178
+ return strapi.db.query(getModelUid('comment-report')).update({
179
+ where: {
180
+ id,
181
+ related: commentId,
182
+ },
183
+ data: { resolved: true }
184
+ });
185
+ },
186
+ });
@@ -0,0 +1,227 @@
1
+ 'use strict';
2
+
3
+ const config = require('./../config');
4
+ const { getPluginService } = require('./../utils/functions');
5
+ const { isNil, isEmpty } = require('lodash');
6
+ const PluginError = require('./../utils/error');
7
+ const {
8
+ isEqualEntity,
9
+ getModelUid,
10
+ getRelatedGroups,
11
+ checkBadWords,
12
+ isValidUserContext,
13
+ resolveUserContextError,
14
+ } = require('./utils/functions');
15
+ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
16
+
17
+ /**
18
+ * Comments Plugin - Client services
19
+ */
20
+
21
+ module.exports = ({ strapi }) => ({
22
+
23
+ getCommonService() {
24
+ return getPluginService('common');
25
+ },
26
+
27
+ // Create a comment
28
+ async create(relation, data, user = undefined) {
29
+ const { content, threadOf, related } = data;
30
+ const singleRelationFulfilled = related && REGEX.relatedUid.test(related);
31
+
32
+ if (relation !== related) {
33
+ throw new PluginError(400, `Insonsistent relation uid`);
34
+ }
35
+
36
+ if(!singleRelationFulfilled) {
37
+ throw new PluginError(400, `Field "related" got incorrect format, use format like "api::<collection name>.<content type name>:<entity id>"`);
38
+ }
39
+
40
+ const [ uid, relatedStringId ] = getRelatedGroups(related);
41
+ const relatedId = parseInt(relatedStringId, 10);
42
+
43
+ const relatedEntity = await strapi.db.query(uid).findOne(relatedId);
44
+ if (!relatedEntity) {
45
+ throw new PluginError(400, `Relation for field "related" does not exist. Check your payload please.`);
46
+ }
47
+
48
+ const linkToThread = !isNil(threadOf) ? !!await this.getCommonService().findOne({
49
+ id: threadOf,
50
+ related,
51
+ }) : true;
52
+ const validContext = isValidUserContext(user);
53
+
54
+ if (!linkToThread) {
55
+ throw new PluginError(400, 'Thread does not exist');
56
+ }
57
+
58
+ if (!validContext) {
59
+ throw resolveUserContextError(user);
60
+ }
61
+
62
+ if (checkBadWords(content) && singleRelationFulfilled) {
63
+ const { author, authorUser, ...rest } = data;
64
+ let authorData = {};
65
+ if (validContext && user) {
66
+ authorData = {
67
+ authorUser: user?.id || authorUser,
68
+ };
69
+ } else if (authorUser) {
70
+ authorData = {
71
+ authorUser,
72
+ };
73
+ } else {
74
+ if (author.email && !REGEX.email.test(author.email)) {
75
+ throw new PluginError(400, 'Field: "author.email". Author e-mail is not valid value. Check your payload');
76
+ }
77
+
78
+ authorData = {
79
+ authorId: author.id,
80
+ authorName: author.name,
81
+ authorEmail: author.email,
82
+ authorAvatar: author.avatar,
83
+ };
84
+ }
85
+
86
+ if (!isEmpty(authorData) && !authorData.authorId) {
87
+ throw new PluginError(400, 'Object: "author" is invalid. Check your payload');
88
+ }
89
+
90
+ if (relatedEntity.requireCommentsApproval && data.approvalStatus !== APPROVAL_STATUS.PENDING) {
91
+ throw new PluginError(400, 'Invalid approval status');
92
+ }
93
+
94
+ const entity = await strapi.db.query(getModelUid('comment')).create({
95
+ data: {
96
+ ...rest,
97
+ ...authorData,
98
+ related,
99
+ }
100
+ });
101
+ return this.getCommonService().sanitizeCommentEntity(entity);
102
+ }
103
+ throw new PluginError(400, 'No content received');
104
+ },
105
+
106
+ // Update a comment
107
+ async update(id, relation, data, user) {
108
+ const { content, related } = data;
109
+
110
+ const singleRelationFulfilled = related && REGEX.relatedUid.test(related)
111
+
112
+ if (relation !== related) {
113
+ throw new PluginError(400, `Insonsistent relation uid`);
114
+ }
115
+
116
+ if(!singleRelationFulfilled) {
117
+ throw new PluginError(400, `Field "related" got incorrect format, use format like "api::<collection name>.<content type name>:<entity id>"`);
118
+ }
119
+
120
+ const existingEntity = await this.getCommonService().findOne({
121
+ id,
122
+ related,
123
+ });
124
+ const validContext = isValidUserContext(user);
125
+
126
+ if (!validContext) {
127
+ throw resolveUserContextError(user);
128
+ }
129
+
130
+ if (isEqualEntity(existingEntity, data, user) && content) {
131
+ if (checkBadWords(content)) {
132
+ const entity = await strapi.db.query(getModelUid('comment')).update({
133
+ where: { id },
134
+ data: { content },
135
+ });
136
+ return this.getCommonService().sanitizeCommentEntity(entity);
137
+ }
138
+ }
139
+ throw new PluginError(409, 'Action on that entity is not allowed');
140
+ },
141
+
142
+ // Report abuse in comment
143
+ async reportAbuse(id, relation, payload, user) {
144
+ if (!isValidUserContext(user)) {
145
+ throw resolveUserContextError(user);
146
+ }
147
+ const existingEntity = await this.getCommonService().findOne({
148
+ id,
149
+ related: relation,
150
+ });
151
+ if (existingEntity) {
152
+ const entity = await strapi.db.query(getModelUid('comment-report')).create({
153
+ data: {
154
+ ...payload,
155
+ resolved: false,
156
+ related: id,
157
+ }
158
+ });
159
+ if (entity) {
160
+ try {
161
+ await this.sendAbuseReportEmail(entity.reason, entity.content); // Could also add some info about relation
162
+ } catch (err) {
163
+ throw err;
164
+ }
165
+ } else {
166
+ throw new PluginError(500, 'Report cannot be created');
167
+ }
168
+ return entity;
169
+ }
170
+ throw new PluginError(409, 'Action on that entity is not allowed');
171
+ },
172
+
173
+ async markAsRemoved(relationId, commentId, authorId){
174
+ const entity = await this.getCommonService().findOne({
175
+ id: commentId,
176
+ related: relationId,
177
+ $or: [{ authorUser: authorId }, { authorId }],
178
+ });
179
+ if (!entity){
180
+ throw strapi.errors.notFound('Entity not exist');
181
+ }
182
+ return strapi.db.query(getModelUid('comment'))
183
+ .update({ id: commentId, relatedSlug: relationId }, { removed: true })
184
+ .then(({ id }) => this.markAsRemovedNested(id, true))
185
+ .then(() => ({ id: commentId }));
186
+ },
187
+
188
+ async sendAbuseReportEmail(reason, content) {
189
+ const rolesToBeNotified = this.getCommonService().getConfig('moderatorRoles') || ['strapi-super-admin'];
190
+
191
+ const adminUserModel = strapi.db.query('admin::user');
192
+ const emails = await adminUserModel.findMany({
193
+ where: {
194
+ roles: { code: rolesToBeNotified },
195
+ },
196
+ }).then(items => items.map(_ => _.email));
197
+
198
+ const superAdmin = await strapi.db.query('admin::user')
199
+ .findOne({
200
+ where: {
201
+ roles: { code: 'strapi-super-admin' },
202
+ },
203
+ });
204
+
205
+ if (emails.length > 0) {
206
+ try {
207
+ await strapi.plugin('email').service('email').send({
208
+ to: emails,
209
+ from: superAdmin.email,
210
+ subject: 'New abuse report on comment',
211
+ text: `
212
+ There was a new abuse report on your app.
213
+ Reason: ${reason}
214
+ Message: ${content}
215
+ `,
216
+ });
217
+ } catch(err) {
218
+ strapi.log.error(err);
219
+ throw err;
220
+ }
221
+ }
222
+ },
223
+
224
+ markAsRemovedNested(id, status){
225
+ return this.getCommonService().modifiedNestedNestedComments(id, 'removed', status);
226
+ },
227
+ });
@@ -0,0 +1,191 @@
1
+ 'use strict';
2
+
3
+ const { isArray, isEmpty, uniq, get, first } = require('lodash');
4
+ // const { sanitizeEntity } = require('strapi-utils');
5
+ const PluginError = require('./../utils/error');
6
+ const {
7
+ extractMeta,
8
+ getModelUid,
9
+ getRelatedGroups,
10
+ buildNestedStructure,
11
+ filterOurResolvedReports
12
+ } = require('./utils/functions');
13
+
14
+ /**
15
+ * Comments Plugin - common services
16
+ */
17
+
18
+ module.exports = ({ strapi }) => ({
19
+ getAssociationModel(ormModel, related) {
20
+ const relatedAssociation = ormModel.associations.find(_ => _.alias === 'related');
21
+ if (relatedAssociation) {
22
+ return relatedAssociation.related.find(rel => rel.collectionName === related);
23
+ }
24
+ return null;
25
+ },
26
+
27
+ getMorphModel() {
28
+ const ormModel = strapi.query(getModelUid('comment'));
29
+ return ormModel.model.morph;
30
+ },
31
+
32
+ isMongoDB(){
33
+ return strapi.query(getModelUid('comment'))?.model?.orm === 'mongoose';
34
+ },
35
+
36
+ getMorphData(query) {
37
+ return this
38
+ .getMorphModel()
39
+ .query(function () {
40
+ this.where(query);
41
+ })
42
+ .fetchAll()
43
+ .then((entities) => entities.map(entity => entity.toJSON()));
44
+ },
45
+
46
+ checkEntityRelation(uid, entityId) {
47
+ const result = parsedRelation.some(ref => {
48
+ const model = strapi.query(ref);
49
+ return model && model.associations.length &&
50
+ model.associations.some(_ => _.plugin === 'comments' && _.collection === 'comment');
51
+ });
52
+ if (!result) {
53
+ throw new PluginError(400, 'Comment not have relation to this type content type.');
54
+ }
55
+ },
56
+
57
+ getConfig(prop) {
58
+ let queryProp = prop;
59
+ if (prop && isArray(prop)) {
60
+ queryProp = prop.join('.');
61
+ }
62
+ return strapi.config.get(`plugin.comments${ queryProp ? '.' + queryProp : ''}`);
63
+ },
64
+
65
+ // Find comments in the flat structure
66
+ async findAllFlat(query, isAdmin = false, relatedEntity = null) {
67
+ const criteria = {
68
+ ...query,
69
+ $or: [{ removed: { $null: true } }, { removed: false }],
70
+ }
71
+
72
+ if (isAdmin){
73
+ delete criteria.$or;
74
+ }
75
+
76
+ const populate = { authorUser: true, reports: true };
77
+
78
+ const entries = await strapi.db.query(getModelUid('comment'))
79
+ .findMany({
80
+ where: {
81
+ ...criteria,
82
+ },
83
+ populate,
84
+ });
85
+
86
+ const entriesWithThreads = await Promise.all(entries.map(async _ => {
87
+ const [nestedEntries, count] = await strapi.db.query(getModelUid('comment'))
88
+ .findWithCount({
89
+ where: {
90
+ threadOf: _.id
91
+ }
92
+ })
93
+ return { id: _.id, itemsInTread: count, firstThreadItemId: first(nestedEntries)?.id }
94
+ }));
95
+
96
+ const relatedEntities = relatedEntity !== null ? [relatedEntity] : await this.findRelatedEntitiesFor([...entries]);
97
+ const result = entries
98
+ .map(_ => {
99
+ const threadedItem = entriesWithThreads.find(item => item.id === _.id);
100
+ return filterOurResolvedReports(this.sanitizeCommentEntity({
101
+ ..._,
102
+ threadOf: query.threadOf || _.threadOf || null,
103
+ gotThread: (threadedItem?.itemsInTread || 0) > 0,
104
+ threadFirstItemId: threadedItem?.firstThreadItemId,
105
+ }))
106
+ });
107
+
108
+ if (relatedEntities.filter(_ => _).length > 0) {
109
+ return result.map(_ => this.mergeRelatedEntityTo(_, relatedEntities));
110
+ }
111
+ return result;
112
+ },
113
+
114
+ // Find comments and create relations tree structure
115
+ async findAllInHierarchy ({
116
+ query,
117
+ startingFromId = null,
118
+ dropBlockedThreads = false,
119
+ isAdmin = false,
120
+ }, relatedEntity) {
121
+ const entities = await this.findAllFlat(query, isAdmin, relatedEntity);
122
+ return buildNestedStructure(entities, startingFromId, 'threadOf', dropBlockedThreads, false);
123
+ },
124
+
125
+ // Find single comment
126
+ async findOne(criteria) {
127
+ const entity = await strapi.db.query(getModelUid('comment'))
128
+ .findOne({
129
+ where: criteria
130
+ }, ['related', 'reports']);
131
+ if (!entity){
132
+ throw strapi.errors.notFound();
133
+ }
134
+ return filterOurResolvedReports(this.sanitizeCommentEntity(entity));
135
+ },
136
+
137
+ // Find all related entiries
138
+ async findRelatedEntitiesFor(entities = []) {
139
+ return Promise.all(uniq(entities.map(_ => _.related)).map(async _ => {
140
+ const [relatedUid, relatedStringId] = getRelatedGroups(_);
141
+ const relatedId = parseInt(relatedStringId, 10);
142
+ const relatedEntity = await strapi.db.query(relatedUid).findOne({
143
+ where: { id: relatedId }
144
+ });
145
+ return {
146
+ ...relatedEntity,
147
+ uid: relatedUid,
148
+ };
149
+ }))
150
+ },
151
+
152
+ // Merge related entity with comment
153
+ mergeRelatedEntityTo(entity, relatedEntities = []) {
154
+ return {
155
+ ...entity,
156
+ related: relatedEntities.find(relatedEntity => entity.related === `${relatedEntity.uid}:${relatedEntity.id}`)
157
+ };
158
+ },
159
+
160
+ async modifiedNestedNestedComments(id, fieldName, value) {
161
+ try {
162
+ const entitiesToChange = await strapi.bd.query(getModelUid('comment'))
163
+ .findMany({
164
+ where: { threadOf: id }
165
+ });
166
+ const changedEntities = await strapi.bd.query(getModelUid('comment'))
167
+ .updateMany({
168
+ where: { id: entitiesToChange.map(_ => _.id) },
169
+ data: { [fieldName]: value }
170
+ });
171
+ if ((entitiesToChange.length === changedEntities.length) && (changedEntities.length > 0)) {
172
+ const nestedTransactions = await Promise.all(
173
+ changedEntities.map(item => this.modifiedNestedNestedComments(item.id, fieldName, value))
174
+ );
175
+ return nestedTransactions.length === changedEntities.length;
176
+ }
177
+ return true;
178
+ } catch (e) {
179
+ return false;
180
+ }
181
+ },
182
+
183
+ sanitizeCommentEntity: (entity) => ({
184
+ ...entity,
185
+ }),
186
+
187
+ // sanitizeCommentEntity: (entity) => ({
188
+ // ...entity,
189
+ // authorUser: sanitizeEntity(entity.authorUser, { model: strapi.plugins['users-permissions'].models.user }),
190
+ // }),
191
+ });
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ const adminServices = require('./admin');
4
+ const clientServices = require('./client');
5
+ const commonServices = require('./common');
6
+
7
+ module.exports = {
8
+ admin: adminServices,
9
+ client: clientServices,
10
+ common: commonServices,
11
+ };
@@ -17,11 +17,9 @@ beforeEach(() => {
17
17
  }
18
18
  },
19
19
  config: {
20
- custom: {
21
- plugins: {
22
- comments: {
23
- enableUsers: false,
24
- }
20
+ plugins: {
21
+ comments: {
22
+ enableUsers: false,
25
23
  }
26
24
  }
27
25
  }
@@ -31,7 +29,7 @@ beforeEach(() => {
31
29
  })
32
30
 
33
31
  describe('Test Comments service functions utils', () => {
34
-
32
+
35
33
  describe('Bad Words filtering', () => {
36
34
  const text = 'Lorem ipsum dolor sit fuck amet';
37
35
 
@@ -55,7 +53,7 @@ describe('Test Comments service functions utils', () => {
55
53
  });
56
54
 
57
55
  test('Should skip bad words filtering because of configuration change', () => {
58
- global.strapi.config.custom.plugins.comments.badWords = false;
56
+ global.strapi.config.plugins.comments.badWords = false;
59
57
 
60
58
  expect(checkBadWords(text)).toEqual(text);
61
59
  });
@@ -74,13 +72,13 @@ describe('Test Comments service functions utils', () => {
74
72
 
75
73
  describe('Validating user context', () => {
76
74
  test('Context should be skipped based on config', () => {
77
- global.strapi.config.custom.plugins.comments.enableUsers = false;
75
+ global.strapi.config.plugins.comments.enableUsers = false;
78
76
  expect(isValidUserContext(undefined)).toEqual(true);
79
77
  expect(isValidUserContext({ id: 1 })).toEqual(true);
80
78
  });
81
79
 
82
80
  test('Context should be verified based on input', () => {
83
- global.strapi.config.custom.plugins.comments.enableUsers = true;
81
+ global.strapi.config.plugins.comments.enableUsers = true;
84
82
  expect(isValidUserContext(undefined)).toEqual(false);
85
83
  expect(isValidUserContext({ id: 1 })).toEqual(true);
86
84
  });