strapi-plugin-comments 2.0.0-beta.6 → 2.0.0-beta.7

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 (43) hide show
  1. package/README.md +310 -7
  2. package/admin/src/components/DiscussionThreadItem/index.js +4 -8
  3. package/admin/src/components/DiscussionThreadItemMeta/index.js +16 -5
  4. package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +8 -1
  5. package/admin/src/pages/Discover/index.js +4 -1
  6. package/admin/src/translations/en.json +1 -0
  7. package/admin/src/utils/index.js +2 -0
  8. package/admin/src/utils/renderInitials.js +5 -0
  9. package/package.json +2 -1
  10. package/server/config/index.js +5 -0
  11. package/server/controllers/client.js +4 -19
  12. package/server/controllers/utils/parsers.js +17 -0
  13. package/server/graphql/index.js +26 -0
  14. package/server/graphql/mutations/createAbuseReport.js +20 -0
  15. package/server/graphql/mutations/createComment.js +20 -0
  16. package/server/graphql/mutations/index.js +21 -0
  17. package/server/graphql/mutations/removeComment.js +20 -0
  18. package/server/graphql/mutations/updateComment.js +20 -0
  19. package/server/graphql/queries/findAllFlat.js +29 -0
  20. package/server/graphql/queries/findAllInHierarchy.js +31 -0
  21. package/server/graphql/queries/index.js +19 -0
  22. package/server/graphql/resolvers-config.js +10 -0
  23. package/server/graphql/types/comment-author-user.js +9 -0
  24. package/server/graphql/types/comment-author.js +9 -0
  25. package/server/graphql/types/comment-nested.js +16 -0
  26. package/server/graphql/types/comment.js +16 -0
  27. package/server/graphql/types/create-comment-author.js +9 -0
  28. package/server/graphql/types/create-comment.js +9 -0
  29. package/server/graphql/types/create-report.js +9 -0
  30. package/server/graphql/types/id.js +24 -0
  31. package/server/graphql/types/identify-comment-author.js +6 -0
  32. package/server/graphql/types/index.js +17 -0
  33. package/server/graphql/types/remove-comment.js +8 -0
  34. package/server/graphql/types/report-reason.js +10 -0
  35. package/server/graphql/types/report.js +11 -0
  36. package/server/graphql/types/update-comment.js +9 -0
  37. package/server/register.js +3 -1
  38. package/server/services/admin.js +8 -8
  39. package/server/services/client.js +35 -27
  40. package/server/services/common.js +10 -4
  41. package/server/services/graphql.js +190 -0
  42. package/server/services/index.js +2 -0
  43. package/server/services/utils/functions.js +17 -3
@@ -0,0 +1,21 @@
1
+ 'use strict'
2
+
3
+ module.exports = (context) => {
4
+ const mutations = {
5
+ createComment: require('./createComment'),
6
+ updateComment: require('./updateComment'),
7
+ removeComment: require('./removeComment'),
8
+ createAbuseReport: require('./createAbuseReport'),
9
+ };
10
+
11
+ return context.nexus.extendType({
12
+ type: 'Mutation',
13
+ definition(t) {
14
+ for (const [name, configFactory] of Object.entries(mutations)) {
15
+ const config = configFactory(context);
16
+
17
+ t.field(name, config);
18
+ }
19
+ },
20
+ });
21
+ };
@@ -0,0 +1,20 @@
1
+ 'use strict'
2
+
3
+ const { getPluginService } = require('../../utils/functions');
4
+
5
+ module.exports = ({ nexus }) => {
6
+ const { nonNull } = nexus;
7
+
8
+ return {
9
+ type: nonNull('CommentSingle'),
10
+ args: {
11
+ input: nonNull('RemoveComment'),
12
+ },
13
+ async resolve(obj, args) {
14
+ const { input } = args;
15
+ const { id, relation, author } = input;
16
+ return await getPluginService('client')
17
+ .markAsRemoved(id, relation, author?.id);
18
+ },
19
+ };
20
+ }
@@ -0,0 +1,20 @@
1
+ 'use strict'
2
+
3
+ const { getPluginService } = require('../../utils/functions');
4
+
5
+ module.exports = ({ nexus }) => {
6
+ const { nonNull } = nexus;
7
+
8
+ return {
9
+ type: nonNull('CommentSingle'),
10
+ args: {
11
+ input: nonNull('UpdateComment'),
12
+ },
13
+ async resolve(obj, args) {
14
+ const { input } = args;
15
+ const { id, relation, ...body } = input;
16
+ return await getPluginService('client')
17
+ .update(id, relation, body);
18
+ },
19
+ };
20
+ }
@@ -0,0 +1,29 @@
1
+ 'use strict'
2
+
3
+ const contentType = require('../../../content-types/comment');
4
+ const { flatInput } = require('../../controllers/utils/parsers');
5
+ const { getPluginService } = require('../../utils/functions');
6
+
7
+ module.exports = ({ strapi, nexus }) => {
8
+ const { nonNull, list, stringArg } = nexus;
9
+ const { service: getService } = strapi.plugin('graphql');
10
+ const { args } = getService('internals');
11
+
12
+ return {
13
+ type: nonNull(list('CommentSingle')),
14
+ args: {
15
+ relation: nonNull(stringArg()),
16
+ filters: getPluginService('gql').buildContentTypeFilters(contentType),
17
+ pagination: args.PaginationArg,
18
+ sort: args.SortArg,
19
+ },
20
+ async resolve(obj, args) {
21
+ const { relation, filters } = args;
22
+ return await getPluginService('common')
23
+ .findAllFlat(flatInput(
24
+ relation,
25
+ getPluginService('gql').graphQLFiltersToStrapiQuery(filters, contentType)
26
+ ), undefined);
27
+ },
28
+ };
29
+ }
@@ -0,0 +1,31 @@
1
+ 'use strict'
2
+
3
+ const contentType = require('../../../content-types/comment');
4
+ const { flatInput } = require('../../controllers/utils/parsers');
5
+ const { getPluginService } = require('../../utils/functions');
6
+
7
+ module.exports = ({ strapi, nexus }) => {
8
+ const { nonNull, list, stringArg } = nexus;
9
+ const { service: getService } = strapi.plugin('graphql');
10
+ const { args } = getService('internals');
11
+
12
+ return {
13
+ type: nonNull(list('CommentNested')),
14
+ args: {
15
+ relation: nonNull(stringArg()),
16
+ // filters: filtersArg,
17
+ sort: args.SortArg,
18
+ },
19
+ async resolve(obj, args) {
20
+ const { relation, filters } = args;
21
+ return await getPluginService('common')
22
+ .findAllInHierarchy({
23
+ ...flatInput(
24
+ relation,
25
+ getPluginService('gql').graphQLFiltersToStrapiQuery(filters, contentType)
26
+ ),
27
+ dropBlockedThreads: true,
28
+ });
29
+ },
30
+ };
31
+ }
@@ -0,0 +1,19 @@
1
+ 'use strict'
2
+
3
+ module.exports = (context) => {
4
+ const queries = {
5
+ findAllFlat: require('./findAllFlat'),
6
+ findAllInHierarchy: require('./findAllInHierarchy'),
7
+ };
8
+
9
+ return context.nexus.extendType({
10
+ type: 'Query',
11
+ definition(t) {
12
+ for (const [name, configFactory] of Object.entries(queries)) {
13
+ const config = configFactory(context);
14
+
15
+ t.field(name, config);
16
+ }
17
+ },
18
+ });
19
+ };
@@ -0,0 +1,10 @@
1
+ 'use strict'
2
+
3
+ module.exports = ({ }) => ({
4
+ 'Query.findAllFlat': { auth: false },
5
+ 'Query.findAllInHierarchy': { auth: false },
6
+ 'Mutation.createComment': { auth: false },
7
+ 'Mutation.updateComment': { auth: false },
8
+ 'Mutation.removeComment': { auth: false },
9
+ 'Mutation.createAbuseReport': { auth: false },
10
+ });
@@ -0,0 +1,9 @@
1
+ module.exports = ({ nexus }) => nexus.objectType({
2
+ name: "CommentAuthorUser",
3
+ definition(t) {
4
+ t.id("id")
5
+ t.nonNull.string("firstName")
6
+ t.string("lastName")
7
+ t.string("email")
8
+ }
9
+ })
@@ -0,0 +1,9 @@
1
+ module.exports = ({ nexus }) => nexus.objectType({
2
+ name: "CommentAuthor",
3
+ definition(t) {
4
+ t.id("id")
5
+ t.nonNull.string("name")
6
+ t.string("email")
7
+ t.string("avatar")
8
+ }
9
+ })
@@ -0,0 +1,16 @@
1
+ module.exports = ({ nexus }) => nexus.objectType({
2
+ name: "CommentNested",
3
+ definition(t) {
4
+ t.id("id")
5
+ t.nonNull.string("content")
6
+ t.boolean("blocked")
7
+ t.boolean("blockedThread")
8
+ t.string("approvalStatus")
9
+ t.field("threadOf", { type: 'CommentSingle' })
10
+ t.list.field("children", { type: 'CommentNested' })
11
+ t.field("authorUser", { type: 'CommentAuthorUser' })
12
+ t.field("author", { type: 'CommentAuthor' })
13
+ t.string("createdAt")
14
+ t.string("updatedAt")
15
+ }
16
+ })
@@ -0,0 +1,16 @@
1
+ module.exports = ({ nexus }) => nexus.objectType({
2
+ name: "CommentSingle",
3
+ definition(t) {
4
+ t.id("id")
5
+ t.nonNull.string("content")
6
+ t.boolean("blocked")
7
+ t.boolean("blockedThread")
8
+ t.boolean("removed")
9
+ t.string("approvalStatus")
10
+ t.field("threadOf", { type: 'CommentSingle' })
11
+ t.field("authorUser", { type: 'CommentAuthorUser' })
12
+ t.field("author", { type: 'CommentAuthor' })
13
+ t.string("createdAt")
14
+ t.string("updatedAt")
15
+ }
16
+ })
@@ -0,0 +1,9 @@
1
+ module.exports = ({ nexus }) => nexus.inputObjectType({
2
+ name: "CreateCommentAuthor",
3
+ definition(t) {
4
+ t.nonNull.string("id")
5
+ t.nonNull.string("name")
6
+ t.string("email")
7
+ t.string("avatar")
8
+ }
9
+ })
@@ -0,0 +1,9 @@
1
+ module.exports = ({ nexus }) => nexus.inputObjectType({
2
+ name: "CreateComment",
3
+ definition(t) {
4
+ t.nonNull.string("content")
5
+ t.nonNull.string("relation")
6
+ t.id("threadOf")
7
+ t.field("author", { type: 'CreateCommentAuthor' })
8
+ }
9
+ })
@@ -0,0 +1,9 @@
1
+ module.exports = ({ nexus }) => nexus.inputObjectType({
2
+ name: "CreateReport",
3
+ definition(t) {
4
+ t.id("commentId")
5
+ t.nonNull.string("relation")
6
+ t.nonNull.string("content")
7
+ t.field("reason", { type: 'ReportReason' })
8
+ }
9
+ })
@@ -0,0 +1,24 @@
1
+ const { isNumber, parseInt } = require('lodash');
2
+ const { Kind } = require('graphql');
3
+
4
+ module.exports = ({ nexus }) => nexus.scalarType({
5
+ name: "Id",
6
+ asNexusMethod: 'id',
7
+ description: 'Id as string or int',
8
+ parseValue(value) {
9
+ const parsedValue = parseInt(value);
10
+ if (isNumber(parsedValue)) {
11
+ return parsedValue;
12
+ }
13
+ return value;
14
+ },
15
+ serialize(value) {
16
+ return value;
17
+ },
18
+ parseLiteral(ast) {
19
+ if ([Kind.INT, Kind.STRING].includes(ast.kind)) {
20
+ return ast.value;
21
+ }
22
+ return null
23
+ },
24
+ })
@@ -0,0 +1,6 @@
1
+ module.exports = ({ nexus }) => nexus.inputObjectType({
2
+ name: "IdentifyCommentAuthor",
3
+ definition(t) {
4
+ t.nonNull.string("id")
5
+ }
6
+ })
@@ -0,0 +1,17 @@
1
+ const typesFactories = [
2
+ require('./id'),
3
+ require('./comment'),
4
+ require('./comment-nested'),
5
+ require('./comment-author'),
6
+ require('./comment-author-user'),
7
+ require('./create-comment'),
8
+ require('./create-comment-author'),
9
+ require('./update-comment'),
10
+ require('./remove-comment'),
11
+ require('./identify-comment-author'),
12
+ require('./report'),
13
+ require('./report-reason'),
14
+ require('./create-report'),
15
+ ];
16
+
17
+ module.exports = context => typesFactories.map(factory => factory(context));
@@ -0,0 +1,8 @@
1
+ module.exports = ({ nexus }) => nexus.inputObjectType({
2
+ name: "RemoveComment",
3
+ definition(t) {
4
+ t.id("id")
5
+ t.nonNull.string("relation")
6
+ t.field("author", { type: 'IdentifyCommentAuthor' })
7
+ }
8
+ })
@@ -0,0 +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
+ })
@@ -0,0 +1,11 @@
1
+ module.exports = ({ nexus }) => nexus.objectType({
2
+ name: "Report",
3
+ definition(t) {
4
+ t.id("id")
5
+ t.nonNull.string("content")
6
+ t.field("reason", { type: 'ReportReason' })
7
+ t.field("related", { type: 'CommentSingle' })
8
+ t.string("createdAt")
9
+ t.string("updatedAt")
10
+ }
11
+ })
@@ -0,0 +1,9 @@
1
+ module.exports = ({ nexus }) => nexus.inputObjectType({
2
+ name: "UpdateComment",
3
+ definition(t) {
4
+ t.id("id")
5
+ t.nonNull.string("content")
6
+ t.nonNull.string("relation")
7
+ t.field("author", { type: 'IdentifyCommentAuthor' })
8
+ }
9
+ })
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  module.exports = ({ strapi }) => {
4
- // registeration phase
4
+ if (strapi.plugin('graphql')) {
5
+ require('./graphql')({ strapi });
6
+ }
5
7
  };
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { getPluginService, parseParams } = require('./../utils/functions');
4
- const { isEmpty, isNil } = require('lodash');
4
+ const { isEmpty, isNil, isNumber, parseInt } = require('lodash');
5
5
  const PluginError = require('./../utils/error');
6
6
  const {
7
7
  getModelUid,
@@ -137,7 +137,8 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
137
137
  }
138
138
 
139
139
  const [relatedUid, relatedStringId] = getRelatedGroups(entity.related);
140
- const relatedId = parseInt(relatedStringId, 10);
140
+ const parsedRelatedId = parseInt(relatedStringId);
141
+ const relatedId = isNumber(parsedRelatedId) ? parsedRelatedId : relatedStringId;
141
142
  const relatedEntity = await strapi.db.query(relatedUid).findOne({
142
143
  where: { id: relatedId }
143
144
  }).then(_ => ({
@@ -164,15 +165,14 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
164
165
  startingFromId: levelThreadId,
165
166
  isAdmin: true
166
167
  }, false);
167
-
168
- const selectedEntity = this.getCommonService().sanitizeCommentEntity(entity);
168
+ const selectedEntity = this.getCommonService().sanitizeCommentEntity({
169
+ ...entity,
170
+ threadOf: entity.threadOf || null,
171
+ });
169
172
 
170
173
  return {
171
174
  entity: relatedEntity,
172
- selected: {
173
- ...selectedEntity,
174
- threadOf: selectedEntity.threadOf ? selectedEntity.threadOf : null,
175
- },
175
+ selected: selectedEntity,
176
176
  level: entitiesOnSameLevel,
177
177
  };
178
178
  },
@@ -2,7 +2,7 @@
2
2
 
3
3
  const config = require('../config');
4
4
  const { getPluginService } = require('./../utils/functions');
5
- const { isNil, isEmpty } = require('lodash');
5
+ const { isNil, isEmpty, isNumber, parseInt } = require('lodash');
6
6
  const PluginError = require('./../utils/error');
7
7
  const {
8
8
  isEqualEntity,
@@ -34,7 +34,8 @@ module.exports = ({ strapi }) => ({
34
34
  }
35
35
 
36
36
  const [ uid, relatedStringId ] = getRelatedGroups(relation);
37
- const relatedId = parseInt(relatedStringId, 10);
37
+ const parsedRelatedId = parseInt(relatedStringId);
38
+ const relatedId = isNumber(parsedRelatedId) ? parsedRelatedId : relatedStringId;
38
39
 
39
40
  const relatedEntity = await strapi.db.query(uid).findOne(relatedId);
40
41
  if (!relatedEntity) {
@@ -44,7 +45,7 @@ module.exports = ({ strapi }) => ({
44
45
  const isApprovalFlowEnabled = this.getCommonService().getConfig('approvalFlow', []).includes(uid) ||
45
46
  relatedEntity.requireCommentsApproval;
46
47
 
47
- const linkToThread = !isNil(threadOf) ? !!await this.getCommonService().findOne({
48
+ const linkToThread = !isNil(threadOf) ? await this.getCommonService().findOne({
48
49
  id: threadOf,
49
50
  related: relation,
50
51
  }) : true;
@@ -99,20 +100,19 @@ module.exports = ({ strapi }) => ({
99
100
  approvalStatus: isApprovalFlowEnabled ? APPROVAL_STATUS.PENDING : null,
100
101
  }
101
102
  });
102
- return this.getCommonService().sanitizeCommentEntity(entity);
103
+ return this.getCommonService().sanitizeCommentEntity({
104
+ ...entity,
105
+ threadOf: !isNil(threadOf) ? linkToThread : null,
106
+ });
103
107
  }
104
108
  throw new PluginError(400, 'No content received');
105
109
  },
106
110
 
107
111
  // Update a comment
108
112
  async update(id, relation, data, user) {
109
- const { content, related } = data;
110
-
111
- const singleRelationFulfilled = related && REGEX.relatedUid.test(related)
113
+ const { content } = data;
112
114
 
113
- if (relation !== related) {
114
- throw new PluginError(400, `Insonsistent relation uid`);
115
- }
115
+ const singleRelationFulfilled = relation && REGEX.relatedUid.test(relation)
116
116
 
117
117
  if(!singleRelationFulfilled) {
118
118
  throw new PluginError(400, `Field "related" got incorrect format, use format like "api::<collection name>.<content type name>:<entity id>"`);
@@ -120,7 +120,7 @@ module.exports = ({ strapi }) => ({
120
120
 
121
121
  const existingEntity = await this.getCommonService().findOne({
122
122
  id,
123
- related,
123
+ related: relation,
124
124
  });
125
125
  const validContext = isValidUserContext(user);
126
126
 
@@ -133,6 +133,7 @@ module.exports = ({ strapi }) => ({
133
133
  const entity = await strapi.db.query(getModelUid('comment')).update({
134
134
  where: { id },
135
135
  data: { content },
136
+ populate: { threadOf: true },
136
137
  });
137
138
  return this.getCommonService().sanitizeCommentEntity(entity);
138
139
  }
@@ -145,11 +146,11 @@ module.exports = ({ strapi }) => ({
145
146
  if (!isValidUserContext(user)) {
146
147
  throw resolveUserContextError(user);
147
148
  }
148
- const existingEntity = await this.getCommonService().findOne({
149
+ const reportAgainstEntity = await this.getCommonService().findOne({
149
150
  id,
150
151
  related: relation,
151
152
  });
152
- if (existingEntity) {
153
+ if (reportAgainstEntity) {
153
154
  const entity = await strapi.db.query(getModelUid('comment-report')).create({
154
155
  data: {
155
156
  ...payload,
@@ -158,39 +159,46 @@ module.exports = ({ strapi }) => ({
158
159
  }
159
160
  });
160
161
  if (entity) {
162
+ const response = {
163
+ ...entity,
164
+ related: reportAgainstEntity,
165
+ };
161
166
  try {
162
167
  await this.sendAbuseReportEmail(entity.reason, entity.content); // Could also add some info about relation
168
+ return response
163
169
  } catch (err) {
164
- return entity;
170
+ return response;
165
171
  }
166
172
  } else {
167
173
  throw new PluginError(500, 'Report cannot be created');
168
174
  }
169
- return entity;
170
175
  }
171
176
  throw new PluginError(409, 'Action on that entity is not allowed');
172
177
  },
173
178
 
174
- async markAsRemoved(relationId, commentId, authorId){
179
+ async markAsRemoved(id, relation, authorId){
175
180
  const entity = await this.getCommonService().findOne({
176
- id: commentId,
177
- related: relationId,
178
- $or: [{ authorUser: authorId }, { authorId }],
181
+ id,
182
+ related: relation,
183
+ $or: [{ authorUser: authorId }, { authorId }],
179
184
  });
180
- if (!entity){
185
+
186
+ if (!entity) {
181
187
  throw new PluginError(404, 'Entity not exist');
182
188
  }
183
189
 
184
- return strapi.db.query(getModelUid('comment'))
190
+ const removedEntity = await strapi.db.query(getModelUid('comment'))
185
191
  .update({
186
192
  where: {
187
- id: commentId,
188
- related: relationId
193
+ id,
194
+ related: relation
189
195
  },
190
- data: { removed: true }
196
+ data: { removed: true },
197
+ populate: { threadOf: true },
191
198
  })
192
- .then(({ id }) => this.markAsRemovedNested(id, true))
193
- .then(() => ({ id: commentId }));
199
+ await this.markAsRemovedNested(id, true);
200
+
201
+ return this.getCommonService().sanitizeCommentEntity(removedEntity);
194
202
  },
195
203
 
196
204
  async sendAbuseReportEmail(reason, content) {
@@ -229,7 +237,7 @@ module.exports = ({ strapi }) => ({
229
237
  }
230
238
  },
231
239
 
232
- markAsRemovedNested(id, status){
240
+ async markAsRemovedNested(id, status){
233
241
  return this.getCommonService().modifiedNestedNestedComments(id, 'removed', status);
234
242
  },
235
243
  });
@@ -1,13 +1,14 @@
1
1
  'use strict';
2
2
 
3
- const { isArray, uniq, first } = require('lodash');
3
+ const { isArray, isNumber, isObject, first, parseInt } = require('lodash');
4
4
  // const { sanitizeEntity } = require('strapi-utils');
5
5
  const PluginError = require('./../utils/error');
6
6
  const {
7
7
  getModelUid,
8
8
  getRelatedGroups,
9
9
  buildNestedStructure,
10
- filterOurResolvedReports
10
+ filterOurResolvedReports,
11
+ buildAuthorModel,
11
12
  } = require('./utils/functions');
12
13
 
13
14
  /**
@@ -97,9 +98,11 @@ module.exports = ({ strapi }) => ({
97
98
  async findRelatedEntitiesFor(entities = []) {
98
99
  const data = entities.reduce((acc, cur) => {
99
100
  const [relatedUid, relatedStringId] = getRelatedGroups(cur.related);
101
+ const parsedRelatedId = parseInt(relatedStringId);
102
+ const relatedId = isNumber(parsedRelatedId) ? parsedRelatedId : relatedStringId;
100
103
  return {
101
104
  ...acc,
102
- [relatedUid]: [...(acc[relatedUid] || []), parseInt(relatedStringId, 10)]
105
+ [relatedUid]: [...(acc[relatedUid] || []), relatedId]
103
106
  };
104
107
  },
105
108
  {}
@@ -152,7 +155,10 @@ module.exports = ({ strapi }) => ({
152
155
  },
153
156
 
154
157
  sanitizeCommentEntity: (entity) => ({
155
- ...entity,
158
+ ...buildAuthorModel({
159
+ ...entity,
160
+ threadOf: isObject(entity.threadOf) ? buildAuthorModel(entity.threadOf) : entity.threadOf,
161
+ }),
156
162
  }),
157
163
 
158
164
  // sanitizeCommentEntity: (entity) => ({