strapi-plugin-comments 1.0.2 → 2.0.0-beta.3
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/.circleci/config.yml +2 -2
- package/README.md +70 -109
- package/admin/src/components/ConfirmationDialog/index.js +56 -0
- package/admin/src/components/DiscussionStatus/index.js +33 -0
- package/admin/src/components/DiscussionThread/index.js +59 -0
- package/admin/src/components/DiscussionThreadItem/index.js +87 -0
- package/admin/src/components/DiscussionThreadItem/styles.js +88 -0
- package/admin/src/components/DiscussionThreadItemActions/index.js +178 -0
- package/admin/src/components/DiscussionThreadItemActions/styles.js +22 -0
- package/admin/src/components/DiscussionThreadItemMeta/index.js +31 -0
- package/admin/src/components/DiscussionThreadItemWarnings/index.js +141 -0
- package/admin/src/components/DiscussionThreadItemWarnings/styles.js +7 -0
- package/admin/src/components/EntityDetails/index.js +53 -0
- package/admin/src/{containers → components}/Initializer/index.js +4 -4
- package/admin/src/components/LoadingIndicatorOverlay/index.js +15 -0
- package/admin/src/components/LoadingIndicatorOverlay/styles.js +20 -0
- package/admin/src/components/Nav/index.js +45 -0
- package/admin/src/components/PluginIcon/index.js +12 -0
- package/admin/src/components/ReportsReviewModal/index.js +43 -0
- package/admin/src/components/ReportsReviewTable/index.js +146 -0
- package/admin/src/components/TableFilters/index.js +53 -0
- package/admin/src/components/TablePagination/index.js +35 -0
- package/admin/src/components/icons/comments.js +9 -0
- package/admin/src/components/icons/index.js +6 -0
- package/admin/src/components/icons/lock.js +22 -0
- package/admin/src/components/icons/review.js +18 -0
- package/admin/src/components/icons/unlock.js +20 -0
- package/admin/src/index.js +58 -42
- package/admin/src/pages/App/index.js +71 -0
- package/admin/src/pages/App/reducer/actions.js +8 -0
- package/admin/src/pages/App/reducer/constants.js +4 -0
- package/admin/src/pages/App/reducer/index.js +26 -0
- package/admin/src/pages/App/reducer/init.js +7 -0
- package/admin/src/pages/App/reducer/selectors.js +35 -0
- package/admin/src/pages/App/utils/api.js +12 -0
- package/admin/src/pages/ComingSoonPage/index.js +25 -0
- package/admin/src/pages/Details/index.js +109 -0
- package/admin/src/pages/Details/utils/api.js +41 -0
- package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +196 -0
- package/admin/src/pages/Discover/index.js +157 -0
- package/admin/src/pages/Discover/utils/api.js +14 -0
- package/admin/src/pages/Discover/utils/filtersSchema.js +9 -0
- package/admin/src/permissions.js +13 -33
- package/admin/src/pluginId.js +2 -4
- package/admin/src/reducers.js +8 -0
- package/admin/src/translations/en.json +73 -33
- package/admin/src/translations/fr.json +1 -0
- package/admin/src/utils/axiosInstance.js +35 -0
- package/admin/src/utils/constants.js +28 -0
- package/admin/src/utils/getApiUrl.js +5 -0
- package/admin/src/utils/getMessage.js +21 -0
- package/admin/src/utils/getRelatedGroups.js +5 -0
- package/admin/src/utils/getUrl.js +5 -0
- package/admin/src/utils/handleAPIError.js +15 -0
- package/admin/src/utils/index.js +6 -0
- package/codecov.yml +2 -1
- package/content-types/comment.js +97 -0
- package/content-types/index.js +11 -0
- package/content-types/report.js +54 -0
- package/package.json +11 -25
- package/server/bootstrap.js +5 -0
- package/server/config/index.js +10 -0
- package/server/controllers/admin.js +134 -0
- package/server/controllers/client.js +117 -0
- package/server/controllers/index.js +9 -0
- package/{controllers → server/controllers}/utils/__tests__/functions.test.js +0 -0
- package/{controllers → server/controllers}/utils/functions.js +1 -1
- package/server/destroy.js +5 -0
- package/server/index.js +22 -0
- package/server/register.js +5 -0
- package/server/routes/admin.js +92 -0
- package/server/routes/client.js +52 -0
- package/server/routes/index.js +15 -0
- package/server/services/admin.js +215 -0
- package/server/services/client.js +227 -0
- package/server/services/common.js +197 -0
- package/server/services/index.js +11 -0
- package/{services → server/services}/utils/__tests__/functions.test.js +7 -9
- package/{services → server/services}/utils/functions.js +19 -8
- package/server/utils/constants.js +16 -0
- package/{services → server}/utils/error.js +0 -0
- package/server/utils/functions.js +19 -0
- package/strapi-admin.js +3 -0
- package/strapi-server.js +9 -0
- package/admin/src/assets/images/icon-cross-blue.svg +0 -1
- package/admin/src/assets/images/icon_remove.svg +0 -19
- package/admin/src/assets/images/logo.svg +0 -5
- package/admin/src/components/AbuseReportsPopUp/ItemPreviewContainer.js +0 -8
- package/admin/src/components/AbuseReportsPopUp/ModalFooter.js +0 -31
- package/admin/src/components/AbuseReportsPopUp/TableContainer.js +0 -63
- package/admin/src/components/AbuseReportsPopUp/index.js +0 -148
- package/admin/src/components/Container/index.js +0 -7
- package/admin/src/components/EmptyView/index.js +0 -25
- package/admin/src/components/Filters/index.js +0 -151
- package/admin/src/components/Filters/index.styled.js +0 -82
- package/admin/src/components/Filters/utils.js +0 -21
- package/admin/src/components/Item/CardItem.js +0 -30
- package/admin/src/components/Item/CardWrapper.js +0 -10
- package/admin/src/components/Item/index.js +0 -92
- package/admin/src/components/ItemDetails/CardItem.js +0 -33
- package/admin/src/components/ItemDetails/CardLevelCounter.js +0 -46
- package/admin/src/components/ItemDetails/CardLevelCounterLink.js +0 -24
- package/admin/src/components/ItemDetails/CardLevelWrapper.js +0 -22
- package/admin/src/components/ItemDetails/CardWrapper.js +0 -52
- package/admin/src/components/ItemDetails/index.js +0 -151
- package/admin/src/components/ItemFooter/CardItemAuthor.js +0 -10
- package/admin/src/components/ItemFooter/CardItemRelation.js +0 -18
- package/admin/src/components/ItemFooter/Wrapper.js +0 -20
- package/admin/src/components/ItemFooter/index.js +0 -87
- package/admin/src/components/ItemHeader/CardHeaderBlocked.js +0 -24
- package/admin/src/components/ItemHeader/CardHeaderIndicator.js +0 -30
- package/admin/src/components/ItemHeader/CardHeaderIndicatorBlue.js +0 -11
- package/admin/src/components/ItemHeader/CardHeaderIndicatorRed.js +0 -11
- package/admin/src/components/ItemHeader/CardHeaderIndicatorsContainer.js +0 -18
- package/admin/src/components/ItemHeader/CardHeaderReports.js +0 -40
- package/admin/src/components/ItemHeader/Wrapper.js +0 -16
- package/admin/src/components/ItemHeader/index.js +0 -80
- package/admin/src/components/ItemModeration/Wrapper.js +0 -19
- package/admin/src/components/ItemModeration/index.js +0 -47
- package/admin/src/components/List/Container.js +0 -20
- package/admin/src/components/List/index.js +0 -21
- package/admin/src/components/Search/index.js +0 -83
- package/admin/src/containers/App/Wrapper.js +0 -14
- package/admin/src/containers/App/index.js +0 -47
- package/admin/src/containers/DataManagerProvider/actions.js +0 -11
- package/admin/src/containers/DataManagerProvider/index.js +0 -280
- package/admin/src/containers/DataManagerProvider/init.js +0 -5
- package/admin/src/containers/DataManagerProvider/reducer.js +0 -59
- package/admin/src/containers/DetailsView/Wrapper.js +0 -18
- package/admin/src/containers/DetailsView/index.js +0 -100
- package/admin/src/containers/ListView/FadedWrapper.js +0 -42
- package/admin/src/containers/ListView/Footer.js +0 -53
- package/admin/src/containers/ListView/Wrapper.js +0 -17
- package/admin/src/containers/ListView/components.js +0 -138
- package/admin/src/containers/ListView/index.js +0 -45
- package/admin/src/containers/Panel/actions.js +0 -5
- package/admin/src/containers/Panel/index.js +0 -191
- package/admin/src/containers/Panel/index.styled.js +0 -6
- package/admin/src/containers/Panel/reducer.js +0 -44
- package/admin/src/contexts/DataManagerContext.js +0 -5
- package/admin/src/hooks/useDataManager.js +0 -6
- package/admin/src/lifecycles.js +0 -3
- package/admin/src/translations/index.js +0 -7
- package/admin/src/utils/getTrad.js +0 -5
- package/admin/src/utils/getTradId.js +0 -5
- package/config/functions/bootstrap.js +0 -48
- package/config/routes.json +0 -116
- package/config/schema.graphql.js +0 -270
- package/controllers/comments.js +0 -211
- package/models/comment.js +0 -8
- package/models/comment.settings.json +0 -98
- package/models/report.js +0 -8
- package/models/report.settings.json +0 -49
- package/services/comments.js +0 -486
|
@@ -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
|
+
return entity;
|
|
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,197 @@
|
|
|
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, populate = {} }, 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 defaultPopulate = {
|
|
77
|
+
authorUser: true,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const entries = await strapi.db.query(getModelUid('comment'))
|
|
81
|
+
.findMany({
|
|
82
|
+
where: {
|
|
83
|
+
...criteria,
|
|
84
|
+
},
|
|
85
|
+
populate: {
|
|
86
|
+
...defaultPopulate,
|
|
87
|
+
...populate,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const entriesWithThreads = await Promise.all(entries.map(async _ => {
|
|
92
|
+
const [nestedEntries, count] = await strapi.db.query(getModelUid('comment'))
|
|
93
|
+
.findWithCount({
|
|
94
|
+
where: {
|
|
95
|
+
threadOf: _.id
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
return { id: _.id, itemsInTread: count, firstThreadItemId: first(nestedEntries)?.id }
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
const relatedEntities = relatedEntity !== null ? [relatedEntity] : await this.findRelatedEntitiesFor([...entries]);
|
|
102
|
+
const result = entries
|
|
103
|
+
.map(_ => {
|
|
104
|
+
const threadedItem = entriesWithThreads.find(item => item.id === _.id);
|
|
105
|
+
return this.sanitizeCommentEntity({
|
|
106
|
+
..._,
|
|
107
|
+
threadOf: query.threadOf || _.threadOf || null,
|
|
108
|
+
gotThread: (threadedItem?.itemsInTread || 0) > 0,
|
|
109
|
+
threadFirstItemId: threadedItem?.firstThreadItemId,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (relatedEntities.filter(_ => _).length > 0) {
|
|
114
|
+
return result.map(_ => this.mergeRelatedEntityTo(_, relatedEntities));
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
// Find comments and create relations tree structure
|
|
120
|
+
async findAllInHierarchy ({
|
|
121
|
+
query,
|
|
122
|
+
populate = {},
|
|
123
|
+
startingFromId = null,
|
|
124
|
+
dropBlockedThreads = false,
|
|
125
|
+
isAdmin = false,
|
|
126
|
+
}, relatedEntity) {
|
|
127
|
+
const entities = await this.findAllFlat({ query, populate }, isAdmin, relatedEntity);
|
|
128
|
+
return buildNestedStructure(entities, startingFromId, 'threadOf', dropBlockedThreads, false);
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// Find single comment
|
|
132
|
+
async findOne(criteria) {
|
|
133
|
+
const entity = await strapi.db.query(getModelUid('comment'))
|
|
134
|
+
.findOne({
|
|
135
|
+
where: criteria
|
|
136
|
+
}, ['related', 'reports']);
|
|
137
|
+
if (!entity){
|
|
138
|
+
throw strapi.errors.notFound();
|
|
139
|
+
}
|
|
140
|
+
return filterOurResolvedReports(this.sanitizeCommentEntity(entity));
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// Find all related entiries
|
|
144
|
+
async findRelatedEntitiesFor(entities = []) {
|
|
145
|
+
return Promise.all(uniq(entities.map(_ => _.related)).map(async _ => {
|
|
146
|
+
const [relatedUid, relatedStringId] = getRelatedGroups(_);
|
|
147
|
+
const relatedId = parseInt(relatedStringId, 10);
|
|
148
|
+
const relatedEntity = await strapi.db.query(relatedUid).findOne({
|
|
149
|
+
where: { id: relatedId }
|
|
150
|
+
});
|
|
151
|
+
return {
|
|
152
|
+
...relatedEntity,
|
|
153
|
+
uid: relatedUid,
|
|
154
|
+
};
|
|
155
|
+
}))
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
// Merge related entity with comment
|
|
159
|
+
mergeRelatedEntityTo(entity, relatedEntities = []) {
|
|
160
|
+
return {
|
|
161
|
+
...entity,
|
|
162
|
+
related: relatedEntities.find(relatedEntity => entity.related === `${relatedEntity.uid}:${relatedEntity.id}`)
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
async modifiedNestedNestedComments(id, fieldName, value) {
|
|
167
|
+
try {
|
|
168
|
+
const entitiesToChange = await strapi.bd.query(getModelUid('comment'))
|
|
169
|
+
.findMany({
|
|
170
|
+
where: { threadOf: id }
|
|
171
|
+
});
|
|
172
|
+
const changedEntities = await strapi.bd.query(getModelUid('comment'))
|
|
173
|
+
.updateMany({
|
|
174
|
+
where: { id: entitiesToChange.map(_ => _.id) },
|
|
175
|
+
data: { [fieldName]: value }
|
|
176
|
+
});
|
|
177
|
+
if ((entitiesToChange.length === changedEntities.length) && (changedEntities.length > 0)) {
|
|
178
|
+
const nestedTransactions = await Promise.all(
|
|
179
|
+
changedEntities.map(item => this.modifiedNestedNestedComments(item.id, fieldName, value))
|
|
180
|
+
);
|
|
181
|
+
return nestedTransactions.length === changedEntities.length;
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
} catch (e) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
sanitizeCommentEntity: (entity) => ({
|
|
190
|
+
...entity,
|
|
191
|
+
}),
|
|
192
|
+
|
|
193
|
+
// sanitizeCommentEntity: (entity) => ({
|
|
194
|
+
// ...entity,
|
|
195
|
+
// authorUser: sanitizeEntity(entity.authorUser, { model: strapi.plugins['users-permissions'].models.user }),
|
|
196
|
+
// }),
|
|
197
|
+
});
|
|
@@ -17,11 +17,9 @@ beforeEach(() => {
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
config: {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const BadWordsFilter = require('bad-words');
|
|
2
|
-
const PluginError = require('
|
|
2
|
+
const PluginError = require('./../../utils/error');
|
|
3
|
+
const { REGEX } = require('./../../utils/constants')
|
|
3
4
|
const { first, get, isObject, isNil } = require('lodash');
|
|
4
5
|
|
|
5
6
|
const buildNestedStructure = (
|
|
@@ -8,12 +9,11 @@ const buildNestedStructure = (
|
|
|
8
9
|
field = 'parent',
|
|
9
10
|
dropBlockedThreads = false,
|
|
10
11
|
blockNestedThreads = false,
|
|
11
|
-
isMongoDB = false
|
|
12
12
|
) =>
|
|
13
13
|
entities
|
|
14
14
|
.filter(entity => {
|
|
15
15
|
// mongo by default not return `null` for empty data
|
|
16
|
-
if (
|
|
16
|
+
if (entity[field] === null && id === null) {
|
|
17
17
|
return true;
|
|
18
18
|
}
|
|
19
19
|
let data = entity[field];
|
|
@@ -34,12 +34,15 @@ const buildNestedStructure = (
|
|
|
34
34
|
module.exports = {
|
|
35
35
|
isEqualEntity: (existing, data, user) => {
|
|
36
36
|
const { authorUser, authorId } = existing;
|
|
37
|
+
console.log('data', data);
|
|
38
|
+
console.log(existing);
|
|
39
|
+
const { author } = data;
|
|
37
40
|
if (authorUser) {
|
|
38
|
-
const existingUserId =
|
|
39
|
-
const receivedUserId =
|
|
41
|
+
const existingUserId = authorUser?.id || authorUser;
|
|
42
|
+
const receivedUserId = user?.id || author;
|
|
40
43
|
return existingUserId === receivedUserId;
|
|
41
44
|
}
|
|
42
|
-
return authorId ===
|
|
45
|
+
return authorId === author?.id;
|
|
43
46
|
},
|
|
44
47
|
|
|
45
48
|
extractMeta: plugins => {
|
|
@@ -54,13 +57,21 @@ module.exports = {
|
|
|
54
57
|
};
|
|
55
58
|
},
|
|
56
59
|
|
|
60
|
+
getRelatedGroups: related => related.split(REGEX.relatedUid).filter(s => s && s.length > 0),
|
|
61
|
+
|
|
62
|
+
getModelUid: name => {
|
|
63
|
+
return strapi
|
|
64
|
+
.plugin('comments')
|
|
65
|
+
.contentTypes[name]?.uid;
|
|
66
|
+
},
|
|
67
|
+
|
|
57
68
|
filterOurResolvedReports: item => (item ? {
|
|
58
69
|
...item,
|
|
59
70
|
reports: (item.reports || []).filter(report => !report.resolved),
|
|
60
71
|
} : item),
|
|
61
72
|
|
|
62
73
|
checkBadWords: content => {
|
|
63
|
-
const config = get(strapi.config, '
|
|
74
|
+
const config = get(strapi.config, 'plugins.comments.badWords', true);
|
|
64
75
|
if (config) {
|
|
65
76
|
const filter = new BadWordsFilter(isObject(config) ? config : undefined);
|
|
66
77
|
if (content && filter.isProfane(content)) {
|
|
@@ -82,7 +93,7 @@ module.exports = {
|
|
|
82
93
|
buildNestedStructure,
|
|
83
94
|
|
|
84
95
|
isValidUserContext: (user = {}) => {
|
|
85
|
-
|
|
96
|
+
const builtInContextEnabled = get(strapi.config, 'plugins.comments.enableUsers', false);
|
|
86
97
|
return builtInContextEnabled ? !isNil(user.id) : true;
|
|
87
98
|
},
|
|
88
99
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const APPROVAL_STATUS = {
|
|
2
|
+
PENDING: "PENDING",
|
|
3
|
+
APPROVED: "APPROVED",
|
|
4
|
+
REJECTED: "REJECTED",
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const REGEX = {
|
|
8
|
+
uid: new RegExp(/^(?<type>[a-z]+)\:{2}(?<api>[a-z]+)\.{1}(?<contentType>[a-z]+)$/gmi),
|
|
9
|
+
relatedUid: new RegExp(/^(?<uid>[a-z]+\:{2}[a-z]+\.[a-z]+)\:{1}(?<id>[0-9]+)$/gmi),
|
|
10
|
+
email: new RegExp(/\S+@\S+\.\S+/),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
APPROVAL_STATUS,
|
|
15
|
+
REGEX,
|
|
16
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
getPluginService(name) {
|
|
5
|
+
return strapi
|
|
6
|
+
.plugin('comments')
|
|
7
|
+
.service(name);
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
parseParams: params =>
|
|
11
|
+
Object.keys(params).reduce((prev, curr) => {
|
|
12
|
+
const value = params[curr];
|
|
13
|
+
const parsedValue = Number(value);
|
|
14
|
+
return {
|
|
15
|
+
...prev,
|
|
16
|
+
[curr]: isNaN(parsedValue) ? value : parsedValue
|
|
17
|
+
};
|
|
18
|
+
}, {}),
|
|
19
|
+
};
|
package/strapi-admin.js
ADDED
package/strapi-server.js
ADDED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg width="8" height="8" xmlns="http://www.w3.org/2000/svg"><path d="M7.78 6.72L5.06 4l2.72-2.72a.748.748 0 0 0 0-1.06.748.748 0 0 0-1.06 0L4 2.94 1.28.22a.748.748 0 0 0-1.06 0 .748.748 0 0 0 0 1.06L2.94 4 .22 6.72a.748.748 0 0 0 0 1.06.748.748 0 0 0 1.06 0L4 5.06l2.72 2.72a.748.748 0 0 0 1.06 0 .752.752 0 0 0 0-1.06z" fill="#007EFF" fill-rule="evenodd"/></svg>
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
3
|
-
<!-- Generator: Sketch 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
|
|
4
|
-
<title>Icon remove</title>
|
|
5
|
-
<desc>Created with Sketch.</desc>
|
|
6
|
-
<defs></defs>
|
|
7
|
-
<g id="Pages" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
8
|
-
<g id="Content-Manager---List-view" transform="translate(-279.000000, -165.000000)">
|
|
9
|
-
<g id="Container" transform="translate(234.000000, 0.000000)">
|
|
10
|
-
<g id="Add-filters" transform="translate(0.000000, 60.000000)">
|
|
11
|
-
<g id="Icon-remove" transform="translate(45.000000, 105.000000)">
|
|
12
|
-
<rect id="Rectangle-12" stroke="#E3E9F3" x="0.5" y="0.5" width="19" height="19" rx="9.5"></rect>
|
|
13
|
-
<path d="M6,10 L14,10" id="Line-4" stroke="#007EFF" stroke-width="2" stroke-linecap="round"></path>
|
|
14
|
-
</g>
|
|
15
|
-
</g>
|
|
16
|
-
</g>
|
|
17
|
-
</g>
|
|
18
|
-
</g>
|
|
19
|
-
</svg>
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M532 386.2c27.5-27.1 44-61.1 44-98.2 0-80-76.5-146.1-176.2-157.9C368.3 72.5 294.3 32 208 32 93.1 32 0 103.6 0 192c0 37 16.5 71 44 98.2-15.3 30.7-37.3 54.5-37.7 54.9-6.3 6.7-8.1 16.5-4.4 25 3.6 8.5 12 14 21.2 14 53.5 0 96.7-20.2 125.2-38.8 9.2 2.1 18.7 3.7 28.4 4.9C208.1 407.6 281.8 448 368 448c20.8 0 40.8-2.4 59.8-6.8C456.3 459.7 499.4 480 553 480c9.2 0 17.5-5.5 21.2-14 3.6-8.5 1.9-18.3-4.4-25-.4-.3-22.5-24.1-37.8-54.8zm-392.8-92.3L122.1 305c-14.1 9.1-28.5 16.3-43.1 21.4 2.7-4.7 5.4-9.7 8-14.8l15.5-31.1L77.7 256C64.2 242.6 48 220.7 48 192c0-60.7 73.3-112 160-112s160 51.3 160 112-73.3 112-160 112c-16.5 0-33-1.9-49-5.6l-19.8-4.5zM498.3 352l-24.7 24.4 15.5 31.1c2.6 5.1 5.3 10.1 8 14.8-14.6-5.1-29-12.3-43.1-21.4l-17.1-11.1-19.9 4.6c-16 3.7-32.5 5.6-49 5.6-54 0-102.2-20.1-131.3-49.7C338 339.5 416 272.9 416 192c0-3.4-.4-6.7-.7-10C479.7 196.5 528 238.8 528 288c0 28.7-16.2 50.6-29.7 64z"/></svg>
|
|
2
|
-
<!--
|
|
3
|
-
Font Awesome Free 5.2.0 by @fontawesome - https://fontawesome.com
|
|
4
|
-
License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
|
5
|
-
-->
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { ModalFooter as StrapiModalFooter, colors } from 'strapi-helper-plugin';
|
|
2
|
-
|
|
3
|
-
import styled from 'styled-components';
|
|
4
|
-
|
|
5
|
-
const ModalFooter = styled(StrapiModalFooter)`
|
|
6
|
-
section {
|
|
7
|
-
justify-content: flex-end;
|
|
8
|
-
&:last-of-type {
|
|
9
|
-
justify-content: flex-end;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
label {
|
|
13
|
-
display: flex;
|
|
14
|
-
flex-direction: row;
|
|
15
|
-
align-items: center;
|
|
16
|
-
justify-content: center;
|
|
17
|
-
padding: 0;
|
|
18
|
-
margin: 0;
|
|
19
|
-
|
|
20
|
-
color: ${colors.leftMenu.darkGrey};
|
|
21
|
-
font-weight: 600;
|
|
22
|
-
font-style: italic;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
button {
|
|
26
|
-
margin-left: 1rem;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
`;
|
|
30
|
-
|
|
31
|
-
export default ModalFooter;
|