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

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 (52) hide show
  1. package/.circleci/config.yml +1 -1
  2. package/README.md +186 -134
  3. package/__mocks__/initSetup.js +53 -0
  4. package/admin/src/components/ActionButton/styles.js +10 -0
  5. package/admin/src/components/DiscussionThread/index.js +3 -7
  6. package/admin/src/components/DiscussionThreadItem/index.js +25 -70
  7. package/admin/src/components/DiscussionThreadItem/styles.js +4 -79
  8. package/admin/src/components/DiscussionThreadItemActions/index.js +82 -40
  9. package/admin/src/components/DiscussionThreadItemActions/styles.js +1 -12
  10. package/admin/src/components/DiscussionThreadItemApprovalFlowActions/index.js +2 -3
  11. package/admin/src/components/DiscussionThreadItemFooter/index.js +47 -0
  12. package/admin/src/components/DiscussionThreadItemFooter/styles.js +13 -0
  13. package/admin/src/components/DiscussionThreadItemReviewAction/index.js +131 -0
  14. package/admin/src/components/IconButton/styles.js +14 -0
  15. package/admin/src/components/ReportsReviewModal/index.js +1 -1
  16. package/admin/src/components/ReportsReviewTable/index.js +12 -6
  17. package/admin/src/components/StatusBadge/index.js +17 -0
  18. package/admin/src/components/StatusBadge/styles.js +17 -0
  19. package/admin/src/pages/Details/components/DetailsEntity/index.js +42 -24
  20. package/admin/src/pages/Details/components/DetailsFilters/index.js +8 -10
  21. package/admin/src/pages/Details/index.js +6 -15
  22. package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +30 -67
  23. package/admin/src/translations/en.json +13 -15
  24. package/admin/src/utils/resolveCommentStatus.js +4 -4
  25. package/content-types/comment.js +0 -4
  26. package/package.json +8 -5
  27. package/public/assets/logo.png +0 -0
  28. package/public/assets/preview.png +0 -0
  29. package/server/controllers/client.js +7 -2
  30. package/server/controllers/utils/__tests__/functions.test.js +2 -2
  31. package/server/controllers/utils/parsers.js +3 -1
  32. package/server/graphql/mutations/createAbuseReport.js +8 -3
  33. package/server/graphql/mutations/createComment.js +8 -3
  34. package/server/graphql/mutations/removeComment.js +8 -3
  35. package/server/graphql/mutations/updateComment.js +8 -3
  36. package/server/graphql/resolvers-config.js +13 -8
  37. package/server/graphql/types/comment-nested.js +1 -1
  38. package/server/graphql/types/comment.js +0 -1
  39. package/server/graphql/types/create-comment-author.js +1 -1
  40. package/server/graphql/types/identify-comment-author.js +1 -1
  41. package/server/graphql/types/index.js +0 -1
  42. package/server/services/__tests__/common.test.js +165 -0
  43. package/server/services/admin.js +10 -7
  44. package/server/services/client.js +45 -31
  45. package/server/services/common.js +38 -16
  46. package/server/services/utils/__tests__/functions.test.js +9 -73
  47. package/server/services/utils/functions.js +23 -34
  48. package/server/utils/__tests__/functions.test.js +17 -0
  49. package/admin/src/components/DiscussionThreadItemMeta/index.js +0 -42
  50. package/admin/src/components/DiscussionThreadItemWarnings/index.js +0 -147
  51. package/admin/src/components/DiscussionThreadItemWarnings/styles.js +0 -7
  52. package/server/graphql/types/comment-author-user.js +0 -9
@@ -7,12 +7,12 @@
7
7
  "nav.item.discover": "Discover",
8
8
  "nav.item.reports": "Reports",
9
9
  "nav.item.settings": "Settings",
10
- "page.common.item.status.OPEN": "OPEN",
11
- "page.common.item.status.BLOCKED": "BLOCKED",
12
- "page.common.item.status.APPROVED": "APPROVED",
13
- "page.common.item.status.TO_REVIEW": "TO REVIEW ({count, number})",
14
- "page.common.item.status.REJECTED": "REJECTED",
15
- "page.common.item.status.REMOVED": "REMOVED BY AUTHOR",
10
+ "page.common.item.status.OPEN": "Open",
11
+ "page.common.item.status.BLOCKED": "Blocked",
12
+ "page.common.item.status.APPROVED": "Approved",
13
+ "page.common.item.status.TO_REVIEW": "To review ({count, number})",
14
+ "page.common.item.status.REJECTED": "Rejected",
15
+ "page.common.item.status.REMOVED": "Removed by author",
16
16
  "page.updates.header": "Recent updates",
17
17
  "page.discover.header": "Discover threads",
18
18
  "page.discover.header.count": "entries found",
@@ -47,17 +47,17 @@
47
47
  "page.details.panel.discussion.warnings.reports.dialog.content": "Content",
48
48
  "page.details.panel.discussion.warnings.reports.dialog.createdAt": "Issue date",
49
49
  "page.details.panel.discussion.warnings.reports.dialog.status": "Status",
50
- "page.details.panel.discussion.warnings.reports.dialog.status.OPEN": "OPEN",
51
- "page.details.panel.discussion.warnings.reports.dialog.status.RESOLVED": "RESOLVED",
50
+ "page.details.panel.discussion.warnings.reports.dialog.status.OPEN": "Open",
51
+ "page.details.panel.discussion.warnings.reports.dialog.status.RESOLVED": "Resolved",
52
52
  "page.details.panel.discussion.warnings.reports.dialog.actions": "Actions",
53
53
  "page.details.panel.discussion.warnings.reports.dialog.actions.resolve": "Resolve",
54
54
  "page.details.panel.discussion.warnings.reports.dialog.confirmation.success": "Open report has been resolved",
55
- "page.details.panel.discussion.status.blocked": "BLOCKED",
55
+ "page.details.panel.discussion.status.blocked": "Blocked",
56
56
  "page.details.panel.entity": "Entity details",
57
57
  "page.details.panel.status": "Discussion is",
58
- "page.details.panel.status.open": "OPEN",
59
- "page.details.panel.status.blocked": "BLOCKED",
60
- "page.details.panel.status.pending": "PENDING",
58
+ "page.details.panel.status.open": "Open",
59
+ "page.details.panel.status.blocked": "Blocked",
60
+ "page.details.panel.status.pending": "Pending",
61
61
  "page.details.panel.loading": "Fetching entity...",
62
62
  "page.details.actions.comment.block": "Block comment",
63
63
  "page.details.actions.comment.block.confirmation.header": "Moderation: Block comment",
@@ -77,9 +77,7 @@
77
77
  "page.details.actions.comment.approve.confirmation.success": "Comment has been approved",
78
78
  "page.details.actions.comment.reject.confirmation.success": "Comment has been rejected",
79
79
  "page.details.filters.label": "View",
80
- "page.details.filters.removed.visibility": "Comments visibility",
81
- "page.details.filters.removed.show": "Show removed",
82
- "page.details.filters.removed.hide": "Hide removed",
80
+ "page.details.filters.removed.visibility": "Show removed comments",
83
81
  "page.coming.soon": "Wait for what is coming soon...",
84
82
  "compontents.confirmation.dialog.header": "Confirmation",
85
83
  "compontents.confirmation.dialog.description": "Do you really want to perform this action?",
@@ -4,10 +4,6 @@ import { COMMENT_STATUS } from './constants';
4
4
  const resolveCommentStatus = ({ removed, blocked, blockedThread, approvalStatus, reviewFlowEnabled }) => {
5
5
  const gotApprovalFlow = !isNil(approvalStatus);
6
6
 
7
- if (reviewFlowEnabled) {
8
- return COMMENT_STATUS.TO_REVIEW;
9
- }
10
-
11
7
  if (removed) {
12
8
  return COMMENT_STATUS.REMOVED;
13
9
  }
@@ -15,6 +11,10 @@ const resolveCommentStatus = ({ removed, blocked, blockedThread, approvalStatus,
15
11
  if (blocked || blockedThread) {
16
12
  return COMMENT_STATUS.BLOCKED;
17
13
  }
14
+
15
+ if (reviewFlowEnabled) {
16
+ return COMMENT_STATUS.TO_REVIEW;
17
+ }
18
18
 
19
19
  if (gotApprovalFlow) {
20
20
  const status = approvalStatus.toUpperCase();
@@ -48,10 +48,6 @@ module.exports = {
48
48
  target: 'plugin::users-permissions.user',
49
49
  configurable: false,
50
50
  },
51
- authorType: {
52
- type: 'string',
53
- configurable: false
54
- },
55
51
  authorId: {
56
52
  type: 'string',
57
53
  configurable: false
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "strapi-plugin-comments",
3
- "version": "2.0.0-beta.7",
3
+ "version": "2.0.1",
4
4
  "description": "Strapi - Comments plugin",
5
5
  "strapi": {
6
6
  "name": "comments",
7
- "description": "Comments & audience engagement",
7
+ "displayName": "Comments",
8
+ "description": "Powerful Strapi based comments moderation tool for you and your users",
8
9
  "kind": "plugin"
9
10
  },
10
11
  "scripts": {
12
+ "publish": "npm publish --tag latest",
11
13
  "test:unit": "jest --verbose --coverage"
12
14
  },
13
15
  "repository": {
@@ -15,15 +17,16 @@
15
17
  "url": "https://github.com/VirtusLab-Open-Source/strapi-plugin-comments"
16
18
  },
17
19
  "dependencies": {
18
- "@strapi/utils": "^4.0.5",
19
- "@strapi/plugin-graphql": "^4.0.5",
20
+ "@strapi/utils": "^4.0.7",
21
+ "@strapi/plugin-graphql": "^4.0.7",
20
22
  "bad-words": "^3.0.4"
21
23
  },
22
24
  "devDependencies": {
23
25
  "codecov": "^3.7.2",
24
26
  "jest": "^26.0.1",
25
27
  "jest-cli": "^26.0.1",
26
- "jest-styled-components": "^7.0.2"
28
+ "jest-styled-components": "^7.0.2",
29
+ "koa": "2.13.3"
27
30
  },
28
31
  "peerDependencies": {
29
32
  "@strapi/strapi": "4.x"
Binary file
Binary file
@@ -82,14 +82,19 @@ module.exports = {
82
82
  },
83
83
 
84
84
  async removeComment(ctx) {
85
- const { params: { relationId, commentId }, query: { authorId } } = ctx;
86
- if (authorId) {
85
+ const {
86
+ params: { relationId, commentId },
87
+ query: { authorId },
88
+ state: { user },
89
+ } = ctx;
90
+ if (authorId || user?.id) {
87
91
  try {
88
92
  return await this.getService()
89
93
  .markAsRemoved(
90
94
  commentId,
91
95
  relationId,
92
96
  authorId,
97
+ user
93
98
  );
94
99
  } catch (e) {
95
100
  if (!e.isBoom) {
@@ -1,6 +1,6 @@
1
- const PluginError = require('../../../services/utils/error');
1
+ const PluginError = require('../../../utils/error');
2
2
  const ctx = require('koa/lib/context');
3
- const { parseParams, throwError } = require('../functions');
3
+ const { throwError } = require('../functions');
4
4
 
5
5
  jest.mock
6
6
 
@@ -9,7 +9,9 @@ module.exports = {
9
9
  related: relation,
10
10
  },
11
11
  populate: {
12
- threadOf: true,
12
+ threadOf: {
13
+ populate: { authorUser: true },
14
+ },
13
15
  },
14
16
  };
15
17
  },
@@ -10,11 +10,16 @@ module.exports = ({ nexus }) => {
10
10
  args: {
11
11
  input: nonNull('CreateReport'),
12
12
  },
13
- async resolve(obj, args) {
13
+ async resolve(obj, args, ctx) {
14
14
  const { input } = args;
15
+ const { state: { user } = {} } = ctx;
15
16
  const { commentId, relation, ...body } = input;
16
- return await getPluginService('client')
17
- .reportAbuse(commentId, relation, body);
17
+ try {
18
+ return await getPluginService('client')
19
+ .reportAbuse(commentId, relation, body, user);
20
+ } catch(e) {
21
+ throw new Error(e);
22
+ }
18
23
  },
19
24
  };
20
25
  }
@@ -10,11 +10,16 @@ module.exports = ({ nexus }) => {
10
10
  args: {
11
11
  input: nonNull('CreateComment'),
12
12
  },
13
- async resolve(obj, args) {
13
+ async resolve(obj, args, ctx) {
14
14
  const { input } = args;
15
+ const { state: { user } = {} } = ctx;
15
16
  const { relation, ...body } = input;
16
- return await getPluginService('client')
17
- .create(relation, body);
17
+ try {
18
+ return await getPluginService('client')
19
+ .create(relation, body, user);
20
+ } catch(e) {
21
+ throw new Error(e);
22
+ }
18
23
  },
19
24
  };
20
25
  }
@@ -10,11 +10,16 @@ module.exports = ({ nexus }) => {
10
10
  args: {
11
11
  input: nonNull('RemoveComment'),
12
12
  },
13
- async resolve(obj, args) {
13
+ async resolve(obj, args, ctx) {
14
14
  const { input } = args;
15
+ const { state: { user } = {} } = ctx;
15
16
  const { id, relation, author } = input;
16
- return await getPluginService('client')
17
- .markAsRemoved(id, relation, author?.id);
17
+ try {
18
+ return await getPluginService('client')
19
+ .markAsRemoved(id, relation, author?.id, user);
20
+ } catch(e) {
21
+ throw new Error(e);
22
+ }
18
23
  },
19
24
  };
20
25
  }
@@ -10,11 +10,16 @@ module.exports = ({ nexus }) => {
10
10
  args: {
11
11
  input: nonNull('UpdateComment'),
12
12
  },
13
- async resolve(obj, args) {
13
+ async resolve(obj, args, ctx) {
14
14
  const { input } = args;
15
+ const { state: { user } = {} } = ctx;
15
16
  const { id, relation, ...body } = input;
16
- return await getPluginService('client')
17
- .update(id, relation, body);
17
+ try {
18
+ return await getPluginService('client')
19
+ .update(id, relation, body, user);
20
+ } catch(e) {
21
+ throw new Error(e);
22
+ }
18
23
  },
19
24
  };
20
25
  }
@@ -1,10 +1,15 @@
1
1
  'use strict'
2
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
- });
3
+ const { getPluginService } = require("../utils/functions");
4
+
5
+ module.exports = () => {
6
+ const auth = getPluginService('common').getConfig('gql.auth', false);
7
+ return {
8
+ 'Query.findAllFlat': { auth },
9
+ 'Query.findAllInHierarchy': { auth },
10
+ 'Mutation.createComment': { auth },
11
+ 'Mutation.updateComment': { auth },
12
+ 'Mutation.removeComment': { auth },
13
+ 'Mutation.createAbuseReport': { auth },
14
+ };
15
+ };
@@ -6,9 +6,9 @@ module.exports = ({ nexus }) => nexus.objectType({
6
6
  t.boolean("blocked")
7
7
  t.boolean("blockedThread")
8
8
  t.string("approvalStatus")
9
+ t.boolean("removed")
9
10
  t.field("threadOf", { type: 'CommentSingle' })
10
11
  t.list.field("children", { type: 'CommentNested' })
11
- t.field("authorUser", { type: 'CommentAuthorUser' })
12
12
  t.field("author", { type: 'CommentAuthor' })
13
13
  t.string("createdAt")
14
14
  t.string("updatedAt")
@@ -8,7 +8,6 @@ module.exports = ({ nexus }) => nexus.objectType({
8
8
  t.boolean("removed")
9
9
  t.string("approvalStatus")
10
10
  t.field("threadOf", { type: 'CommentSingle' })
11
- t.field("authorUser", { type: 'CommentAuthorUser' })
12
11
  t.field("author", { type: 'CommentAuthor' })
13
12
  t.string("createdAt")
14
13
  t.string("updatedAt")
@@ -1,7 +1,7 @@
1
1
  module.exports = ({ nexus }) => nexus.inputObjectType({
2
2
  name: "CreateCommentAuthor",
3
3
  definition(t) {
4
- t.nonNull.string("id")
4
+ t.nonNull.id("id")
5
5
  t.nonNull.string("name")
6
6
  t.string("email")
7
7
  t.string("avatar")
@@ -1,6 +1,6 @@
1
1
  module.exports = ({ nexus }) => nexus.inputObjectType({
2
2
  name: "IdentifyCommentAuthor",
3
3
  definition(t) {
4
- t.nonNull.string("id")
4
+ t.nonNull.id("id")
5
5
  }
6
6
  })
@@ -3,7 +3,6 @@ const typesFactories = [
3
3
  require('./comment'),
4
4
  require('./comment-nested'),
5
5
  require('./comment-author'),
6
- require('./comment-author-user'),
7
6
  require('./create-comment'),
8
7
  require('./create-comment-author'),
9
8
  require('./update-comment'),
@@ -0,0 +1,165 @@
1
+ const PluginError = require('../../utils/error');
2
+ const { getPluginService } = require('../../utils/functions');
3
+
4
+ jest.mock
5
+
6
+
7
+ const setup = function(config) {
8
+ Object.defineProperty(global, 'strapi', {
9
+ value: require('../../../__mocks__/initSetup')(config),
10
+ writable: true,
11
+ });
12
+ }
13
+
14
+ afterEach(() => {
15
+ Object.defineProperty(global, 'strapi', {});
16
+ })
17
+
18
+ describe('Test Comments service functions utils', () => {
19
+
20
+ describe('Get plugin config', () => {
21
+ beforeEach(() => setup({
22
+ test: 'sample',
23
+ }));
24
+
25
+ test('Should return config value', () => {
26
+ expect(getPluginService('common').getConfig('test')).toEqual('sample');
27
+ });
28
+
29
+ test('Should return default config prop value if not set', () => {
30
+ expect(getPluginService('common').getConfig('another', 'defaultValue')).toEqual('defaultValue');
31
+ });
32
+ });
33
+
34
+ describe('Validate user context', () => {
35
+ beforeEach(() => setup());
36
+
37
+ test('Should context be valid', () => {
38
+ expect(getPluginService('common').isValidUserContext({ id: 1 })).toEqual(true);
39
+ });
40
+
41
+ test('Should context be invalid', () => {
42
+ expect(getPluginService('common').isValidUserContext({ })).toEqual(false);
43
+ });
44
+
45
+ test('Should use fallback', () => {
46
+ expect(getPluginService('common').isValidUserContext()).toEqual(true);
47
+ });
48
+ });
49
+
50
+ describe('Sanitize comments entity', () => {
51
+ beforeEach(() => setup());
52
+
53
+ const sample = {
54
+ id: 1,
55
+ content: 'Test text',
56
+ blocked: false,
57
+ removed: true,
58
+ };
59
+
60
+ const authorSample = {
61
+ id: 1,
62
+ username: 'Joe Doe',
63
+ email: 'test@example.com',
64
+ password: 'xxx',
65
+ };
66
+
67
+ const authorGenericSample = {
68
+ authorId: 1,
69
+ authorName: 'Joe Doe',
70
+ authorEmail: 'test@example.com',
71
+ };
72
+
73
+ const outputAuthorSample = {
74
+ id: 1,
75
+ name: 'Joe Doe',
76
+ email: 'test@example.com',
77
+ };
78
+
79
+ test('Should return exact the same value', () => {
80
+ const input = { ...sample };
81
+ const output = getPluginService('common').sanitizeCommentEntity(input);
82
+ expect(output).toHaveProperty('id', input.id);
83
+ expect(output).toHaveProperty('content', input.content);
84
+ expect(output).toHaveProperty('blocked', input.blocked);
85
+ expect(output).toHaveProperty('removed', input.removed);
86
+ });
87
+
88
+ test('Should return entity with \'author\' property for generic author', () => {
89
+ const input = {
90
+ ...sample,
91
+ ...authorGenericSample,
92
+ };
93
+ const output = getPluginService('common').sanitizeCommentEntity(input);
94
+
95
+ expect(output).toHaveProperty('author');
96
+ expect(output).toHaveProperty('author.id', input.authorId);
97
+ expect(output).toHaveProperty('author.id', outputAuthorSample.id);
98
+ expect(output).toHaveProperty('author.name', input.authorName);
99
+ expect(output).toHaveProperty('author.name', outputAuthorSample.name);
100
+ expect(output).toHaveProperty('author.email', input.authorEmail);
101
+ expect(output).toHaveProperty('author.email', outputAuthorSample.email);
102
+
103
+ expect(output).not.toHaveProperty('authorId');
104
+ expect(output).not.toHaveProperty('authorName');
105
+ expect(output).not.toHaveProperty('authorEmail');
106
+ });
107
+
108
+ test('Should return entity with \'author\' property for Strapi author', () => {
109
+ const input = {
110
+ ...sample,
111
+ authorUser: authorSample,
112
+ };
113
+ const output = getPluginService('common').sanitizeCommentEntity(input);
114
+
115
+ expect(output).toHaveProperty('author');
116
+ expect(output).toHaveProperty('author.id', input.authorUser.id);
117
+ expect(output).toHaveProperty('author.id', outputAuthorSample.id);
118
+ expect(output).toHaveProperty('author.name', input.authorUser.username);
119
+ expect(output).toHaveProperty('author.name', outputAuthorSample.name);
120
+ expect(output).toHaveProperty('author.email', input.authorUser.email);
121
+ expect(output).toHaveProperty('author.email', outputAuthorSample.email);
122
+
123
+ expect(output).not.toHaveProperty('authorUser');
124
+
125
+ expect(output).not.toHaveProperty('author.password');
126
+ });
127
+ });
128
+
129
+ describe('Bad Words filtering', () => {
130
+ beforeEach(() => setup());
131
+
132
+ const text = 'Lorem ipsum dolor sit fuck amet';
133
+
134
+ test('Should find bad words usage and throw error PluginError', () => {
135
+ expect.assertions(7);
136
+ try {
137
+ getPluginService('common').checkBadWords(text);
138
+ } catch (e) {
139
+ expect(getPluginService('common').getConfig('badWords')).toEqual(undefined);
140
+ expect(e).toBeInstanceOf(PluginError);
141
+ expect(e).toHaveProperty('status', 400);
142
+ expect(e).toHaveProperty('name', 'Strapi:Plugin:Comments');
143
+ expect(e).toHaveProperty('message', 'Bad language used! Please polite your comment...');
144
+ expect(e).toHaveProperty('payload');
145
+ expect(e.payload).toEqual(expect.objectContaining({
146
+ content: {
147
+ original: text,
148
+ filtered: text.replace('fuck', '****'),
149
+ }
150
+ }));
151
+ }
152
+ });
153
+ });
154
+
155
+ describe('Bad Words filtering', () => {
156
+ beforeEach(() => setup({ badWords: false }));
157
+
158
+ const text = 'Lorem ipsum dolor sit fuck amet';
159
+
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);
163
+ });
164
+ });
165
+ });
@@ -117,19 +117,24 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
117
117
  }
118
118
  };
119
119
 
120
- const entity = await strapi.db.query(getModelUid('comment')).findOne({
121
- where: {
122
- id,
123
- },
120
+ const defaultPopulate = {
124
121
  populate: {
125
122
  authorUser: true,
126
123
  threadOf: {
127
124
  populate: {
125
+ authorUser: true,
128
126
  ...reportsPopulation
129
127
  },
130
128
  },
131
129
  ...reportsPopulation,
132
130
  },
131
+ }
132
+
133
+ const entity = await strapi.db.query(getModelUid('comment')).findOne({
134
+ where: {
135
+ id,
136
+ },
137
+ ...defaultPopulate,
133
138
  });
134
139
 
135
140
  if (!entity){
@@ -159,9 +164,7 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
159
164
  threadOf: levelThreadId,
160
165
  related: entity.related,
161
166
  },
162
- populate: {
163
- ...reportsPopulation,
164
- },
167
+ ...defaultPopulate,
165
168
  startingFromId: levelThreadId,
166
169
  isAdmin: true
167
170
  }, false);