strapi-plugin-comments 2.2.17 → 2.3.0

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/README.md CHANGED
@@ -372,6 +372,12 @@ Return a flat structure of comments by specified Author for example `Author` wit
372
372
  **Example URL**: `https://localhost:1337/api/comments/author/1` - get comments by `ID:1` of Strapi User
373
373
  **Example URL**: `https://localhost:1337/api/comments/author/1/generic` - get comments by `ID:1` of Generic User
374
374
 
375
+ #### Skipping fields
376
+
377
+ To skip a field from the response you can use a query param called `omit`. It is a list of `Comment` entity fields you want to ignore. For example `?omit[]=author&omit[]=related`.
378
+
379
+ **Remember!** `id` field is required so it will not be skipped.
380
+
375
381
  **Example response body**
376
382
 
377
383
  ```json
@@ -931,6 +937,46 @@ For developers who upgrades their Strapi instance custom field from Comments plu
931
937
 
932
938
  Read more about this feature in [Strapi's docs](https://docs-next.strapi.io/user-docs/latest/plugins/introduction-to-plugins.html#custom-fields).
933
939
 
940
+ ## Model lifecycle hooks
941
+
942
+ Comments plugin allows to register lifecycle hooks for `Comment` and `Comment report` content types.
943
+
944
+ You can read more about lifecycle hooks [here](https://docs.strapi.io/dev-docs/backend-customization/models#lifecycle-hooks). (You can set a listener for all of the hooks).
945
+
946
+ Lifecycle hooks can be register either in `register()` or `bootstrap()` methods of your server. You can register more than one listener for a specified lifecycle hook. For example: you want to do three things on report creation and do not want to handle all of these actions in one big function. You can split logic in as many listeners as you want.
947
+
948
+ Listeners can by sync and `async`.
949
+
950
+ >Be aware that lifecycle hooks registered in `register()` may be fired by plugin's bootstrapping. If you want listen to events triggered after server's startup use `bootstrap()`.
951
+
952
+ Example:
953
+
954
+ ```ts
955
+ const commentsCommonService = strapi
956
+ .plugin("comments")
957
+ .service("common");
958
+
959
+ commentsCommonService.registerLifecycleHook({
960
+ callback: async ({ action, result }) => {
961
+ const saveResult = await logIntoSystem(action, result);
962
+
963
+ console.log(saveResult);
964
+ },
965
+ contentTypeName: "comment",
966
+ hookName: "afterCreate",
967
+ });
968
+
969
+ commentsCommonService.registerLifecycleHook({
970
+ callback: async ({ action, result }) => {
971
+ const saveResult = await logIntoSystem(action, result);
972
+
973
+ console.log(saveResult);
974
+ },
975
+ contentTypeName: "report",
976
+ hookName: "afterCreate",
977
+ });
978
+ ```
979
+
934
980
  ## 🤝 Contributing
935
981
 
936
982
  <div>
@@ -93,6 +93,7 @@ declare const _default: {
93
93
  };
94
94
  };
95
95
  };
96
+ lifecycles: Record<import("../server/utils/types").LifeCycleHookName, import("../types/utils").Effect<import("../server/utils/types").LifeCycleEvent<import("../server/utils/types").LifeCycleHookName, unknown, Record<string, unknown>>>>;
96
97
  };
97
98
  "comment-report": {
98
99
  schema: {
@@ -141,6 +142,7 @@ declare const _default: {
141
142
  };
142
143
  };
143
144
  };
145
+ lifecycles: Record<import("../server/utils/types").LifeCycleHookName, import("../types/utils").Effect<import("../server/utils/types").LifeCycleEvent<import("../server/utils/types").LifeCycleHookName, unknown, Record<string, unknown>>>>;
144
146
  };
145
147
  };
146
148
  export default _default;
@@ -3,14 +3,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const functions_1 = require("../server/utils/functions");
6
7
  const comment_1 = __importDefault(require("./comment"));
7
8
  const report_1 = __importDefault(require("./report"));
8
9
  exports.default = {
9
10
  comment: {
10
11
  schema: comment_1.default,
12
+ lifecycles: (0, functions_1.buildAllHookListeners)("comment", {
13
+ strapi,
14
+ }),
11
15
  },
12
16
  "comment-report": {
13
17
  schema: report_1.default,
18
+ lifecycles: (0, functions_1.buildAllHookListeners)("comment-report", {
19
+ strapi,
20
+ }),
14
21
  },
15
22
  };
16
23
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-comments",
3
- "version": "2.2.17",
3
+ "version": "2.3.0",
4
4
  "description": "Strapi - Comments plugin",
5
5
  "strapi": {
6
6
  "name": "comments",
@@ -15,7 +15,7 @@ const controllers = {
15
15
  async findAllFlat(ctx) {
16
16
  const { params = {}, query, sort, pagination } = ctx;
17
17
  const { relation } = (0, functions_2.parseParams)(params);
18
- const { sort: querySort, pagination: queryPagination, fields, ...filterQuery } = query || {};
18
+ const { sort: querySort, pagination: queryPagination, fields, omit = [], ...filterQuery } = query || {};
19
19
  try {
20
20
  (0, functions_1.assertParamsPresent)(params, ["relation"]);
21
21
  return this.getService("common").findAllFlat((0, parsers_1.flatInput)({
@@ -24,6 +24,7 @@ const controllers = {
24
24
  sort: sort || querySort,
25
25
  pagination: pagination || queryPagination,
26
26
  fields,
27
+ omit,
27
28
  }));
28
29
  }
29
30
  catch (e) {
@@ -33,7 +34,7 @@ const controllers = {
33
34
  async findAllInHierarchy(ctx) {
34
35
  const { params, query, sort } = ctx;
35
36
  const { relation } = (0, functions_2.parseParams)(params);
36
- const { sort: querySort, fields, ...filterQuery } = query || {};
37
+ const { sort: querySort, fields, omit, ...filterQuery } = query || {};
37
38
  try {
38
39
  (0, functions_1.assertParamsPresent)(params, ["relation"]);
39
40
  return await this.getService("common").findAllInHierarchy({
@@ -42,6 +43,7 @@ const controllers = {
42
43
  query: filterQuery,
43
44
  sort: sort || querySort,
44
45
  fields,
46
+ omit,
45
47
  }),
46
48
  dropBlockedThreads: true,
47
49
  });
@@ -53,7 +55,7 @@ const controllers = {
53
55
  async findAllPerAuthor(ctx) {
54
56
  const { params = {}, query, sort, pagination } = ctx;
55
57
  const { id, type } = (0, functions_2.parseParams)(params);
56
- const { sort: querySort, pagination: queryPagination, fields, ...filterQuery } = query || {};
58
+ const { sort: querySort, pagination: queryPagination, fields, omit = [], ...filterQuery } = query || {};
57
59
  try {
58
60
  (0, functions_1.assertParamsPresent)(params, ["id"]);
59
61
  return this.getService("common").findAllPerAuthor((0, parsers_1.flatInput)({
@@ -61,6 +63,7 @@ const controllers = {
61
63
  sort: sort || querySort,
62
64
  pagination: pagination || queryPagination,
63
65
  fields,
66
+ omit,
64
67
  }), id, ![constants_1.AUTHOR_TYPE.GENERIC.toLowerCase(), constants_1.AUTHOR_TYPE.GENERIC].includes(type));
65
68
  }
66
69
  catch (e) {
@@ -6,5 +6,6 @@ export declare const flatInput: <T, TKeys = keyof T>(payload: FlatInput<OnlyStri
6
6
  pagination: any;
7
7
  sort: any;
8
8
  fields: import("strapi-typed").StrapiRequestQueryFieldsClause<OnlyStrings<TKeys>> | undefined;
9
+ omit: string[] | undefined;
9
10
  };
10
11
  //# sourceMappingURL=parsers.d.ts.map
@@ -7,7 +7,7 @@ exports.flatInput = void 0;
7
7
  const error_1 = __importDefault(require("../../utils/error"));
8
8
  const functions_1 = require("../../utils/functions");
9
9
  const flatInput = (payload) => {
10
- const { relation, query, sort, pagination, fields } = payload;
10
+ const { relation, query, sort, pagination, fields, omit } = payload;
11
11
  const { populate = {}, filterBy, filterByValue, ...restQuery } = query;
12
12
  const filters = restQuery?.filters || restQuery;
13
13
  const orOperator = (filters?.$or || []).filter((_) => !Object.keys(_).includes("removed"));
@@ -65,6 +65,7 @@ const flatInput = (payload) => {
65
65
  pagination,
66
66
  sort,
67
67
  fields,
68
+ omit,
68
69
  };
69
70
  };
70
71
  exports.flatInput = flatInput;
@@ -121,7 +121,7 @@ module.exports = ({ strapi }) => ({
121
121
  });
122
122
  const relatedEntities = await this.getCommonService().findRelatedEntitiesFor(entities);
123
123
  const result = entities
124
- .map((_) => (0, functions_2.filterOurResolvedReports)(this.getCommonService().sanitizeCommentEntity(_, [], defaultAuthorUserPopulate?.populate)))
124
+ .map((_) => (0, functions_2.filterOurResolvedReports)(this.getCommonService().sanitizeCommentEntity(_, [], [], defaultAuthorUserPopulate?.populate)))
125
125
  .map((_) => this.getCommonService().mergeRelatedEntityTo(_, relatedEntities));
126
126
  const pageCount = Math.floor(total / pageSize);
127
127
  return {
@@ -197,7 +197,7 @@ module.exports = ({ strapi }) => ({
197
197
  ..._.related,
198
198
  gotThread: isCommentWithThread,
199
199
  }, []),
200
- }, [], defaultAuthorUserPopulate?.populate));
200
+ }, [], [], defaultAuthorUserPopulate?.populate));
201
201
  });
202
202
  const pageCount = Math.floor(total / pageSize);
203
203
  return {
@@ -291,7 +291,7 @@ module.exports = ({ strapi }) => ({
291
291
  const selectedEntity = this.getCommonService().sanitizeCommentEntity({
292
292
  ...entity,
293
293
  threadOf: entity.threadOf || null,
294
- }, [], defaultAuthorUserPopulate?.populate);
294
+ }, [], [], defaultAuthorUserPopulate?.populate);
295
295
  return {
296
296
  entity: relatedEntity,
297
297
  selected: selectedEntity,
@@ -9,6 +9,11 @@ const error_1 = __importDefault(require("./../utils/error"));
9
9
  const functions_1 = require("./utils/functions");
10
10
  const parsers_1 = require("./utils/parsers");
11
11
  const PAGE_SIZE = 10;
12
+ const REQUIRED_FIELDS = ["id"];
13
+ const lifecycleHookListeners = {
14
+ comment: {},
15
+ "comment-report": {}
16
+ };
12
17
  module.exports = ({ strapi }) => ({
13
18
  async getConfig(prop, defaultValue, useLocal = false) {
14
19
  const queryProp = (0, functions_1.buildConfigQueryProp)(prop);
@@ -33,8 +38,9 @@ module.exports = ({ strapi }) => ({
33
38
  const result = strapi.config.get(`plugin.comments${queryProp ? "." + queryProp : ""}`);
34
39
  return (0, lodash_1.isNil)(result) ? defaultValue : result;
35
40
  },
36
- async findAllFlat({ query = {}, populate = {}, sort, pagination, fields, isAdmin = false, }, relatedEntity = null) {
37
- const defaultSelect = ["id", "related"];
41
+ async findAllFlat({ query = {}, populate = {}, sort, pagination, fields, isAdmin = false, omit: baseOmit = [], }, relatedEntity = null) {
42
+ const omit = baseOmit.filter((field) => !REQUIRED_FIELDS.includes(field));
43
+ const defaultSelect = ["id", "related"].filter((field) => !omit.includes(field));
38
44
  const populateClause = {
39
45
  authorUser: true,
40
46
  ...((0, lodash_1.isObject)(populate) ? populate : {}),
@@ -94,9 +100,11 @@ module.exports = ({ strapi }) => ({
94
100
  firstThreadItemId: (0, lodash_1.first)(nestedEntries)?.id,
95
101
  };
96
102
  }));
97
- const relatedEntities = relatedEntity !== null
98
- ? [relatedEntity]
99
- : await this.findRelatedEntitiesFor([...entries]);
103
+ const relatedEntities = omit.includes("related")
104
+ ? []
105
+ : relatedEntity !== null
106
+ ? [relatedEntity]
107
+ : await this.findRelatedEntitiesFor([...entries]);
100
108
  const hasRelatedEntitiesToMap = relatedEntities.filter((_) => _).length > 0;
101
109
  const result = entries.map((_) => {
102
110
  const threadedItem = entriesWithThreads.find((item) => item.id === _.id);
@@ -118,7 +126,7 @@ module.exports = ({ strapi }) => ({
118
126
  threadOf: primitiveThreadOf || _.threadOf,
119
127
  gotThread: (threadedItem?.itemsInTread || 0) > 0,
120
128
  threadFirstItemId: threadedItem?.firstThreadItemId,
121
- }, doNotPopulateAuthor, authorUserPopulate);
129
+ }, doNotPopulateAuthor, omit, authorUserPopulate);
122
130
  });
123
131
  return {
124
132
  data: hasRelatedEntitiesToMap
@@ -127,8 +135,8 @@ module.exports = ({ strapi }) => ({
127
135
  ...((0, lodash_1.isEmpty)(meta) ? {} : { meta }),
128
136
  };
129
137
  },
130
- async findAllInHierarchy({ query, populate = {}, sort, fields, startingFromId = null, dropBlockedThreads = false, isAdmin = false, }, relatedEntity) {
131
- const entities = await this.findAllFlat({ query, populate, sort, fields, isAdmin }, relatedEntity);
138
+ async findAllInHierarchy({ query, populate = {}, sort, fields, startingFromId = null, dropBlockedThreads = false, isAdmin = false, omit = [], }, relatedEntity) {
139
+ const entities = await this.findAllFlat({ query, populate, sort, fields, isAdmin, omit }, relatedEntity);
132
140
  return (0, functions_1.buildNestedStructure)(entities?.data, startingFromId, "threadOf", dropBlockedThreads, false);
133
141
  },
134
142
  async findOne(criteria) {
@@ -225,18 +233,18 @@ module.exports = ({ strapi }) => ({
225
233
  return false;
226
234
  }
227
235
  },
228
- sanitizeCommentEntity(entity, blockedAuthorProps, populate) {
236
+ sanitizeCommentEntity(entity, blockedAuthorProps, omit = [], populate) {
229
237
  const fieldsToPopulate = (0, lodash_1.isArray)(populate)
230
238
  ? populate
231
239
  : Object.keys(populate || {});
232
- return {
240
+ return (0, lodash_1.omit)({
233
241
  ...(0, functions_1.buildAuthorModel)({
234
242
  ...entity,
235
243
  threadOf: (0, lodash_1.isObject)(entity.threadOf)
236
244
  ? (0, functions_1.buildAuthorModel)(entity.threadOf, blockedAuthorProps, fieldsToPopulate)
237
245
  : entity.threadOf,
238
246
  }, blockedAuthorProps, fieldsToPopulate),
239
- };
247
+ }, omit);
240
248
  },
241
249
  isValidUserContext(user) {
242
250
  return user ? !(0, lodash_1.isNil)(user?.id) : true;
@@ -272,5 +280,17 @@ module.exports = ({ strapi }) => ({
272
280
  const enabledCollections = await this.getConfig("enabledCollections", []);
273
281
  return enabledCollections.includes(uid);
274
282
  },
283
+ registerLifecycleHook({ callback, contentTypeName, hookName }) {
284
+ if (!lifecycleHookListeners[contentTypeName][hookName]) {
285
+ lifecycleHookListeners[contentTypeName][hookName] = [];
286
+ }
287
+ lifecycleHookListeners[contentTypeName][hookName]?.push(callback);
288
+ },
289
+ async runLifecycleHook({ contentTypeName, event, hookName }) {
290
+ const hookListeners = lifecycleHookListeners[contentTypeName][hookName] ?? [];
291
+ for (const listener of hookListeners) {
292
+ await listener(event);
293
+ }
294
+ },
275
295
  });
276
296
  //# sourceMappingURL=common.js.map
@@ -1,4 +1,5 @@
1
1
  import { ConfigParamKeys, RegExpCollection } from "../../types";
2
+ import { LifeCycleHookName } from "./types";
2
3
  export declare const CONFIG_PARAMS: ConfigParamKeys;
3
4
  export declare const APPROVAL_STATUS: {
4
5
  PENDING: string;
@@ -10,4 +11,5 @@ export declare const AUTHOR_TYPE: {
10
11
  STRAPI: string;
11
12
  };
12
13
  export declare const REGEX: RegExpCollection;
14
+ export declare const LIFECYCLE_HOOKS: ReadonlyArray<LifeCycleHookName>;
13
15
  //# sourceMappingURL=constants.d.ts.map
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.REGEX = exports.AUTHOR_TYPE = exports.APPROVAL_STATUS = exports.CONFIG_PARAMS = void 0;
3
+ exports.LIFECYCLE_HOOKS = exports.REGEX = exports.AUTHOR_TYPE = exports.APPROVAL_STATUS = exports.CONFIG_PARAMS = void 0;
4
4
  exports.CONFIG_PARAMS = {
5
5
  ENABLED_COLLECTIONS: "enabledCollections",
6
6
  APPROVAL_FLOW: "approvalFlow",
@@ -24,4 +24,24 @@ exports.REGEX = {
24
24
  email: /\S+@\S+\.\S+/,
25
25
  sorting: /^(?<path>[a-z0-9-_\:\.]+)\:+(asc|desc)$/i,
26
26
  };
27
+ exports.LIFECYCLE_HOOKS = [
28
+ "beforeCreate",
29
+ "beforeCreateMany",
30
+ "afterCreate",
31
+ "afterCreateMany",
32
+ "beforeUpdate",
33
+ "beforeUpdateMany",
34
+ "afterUpdate",
35
+ "afterUpdateMany",
36
+ "beforeDelete",
37
+ "beforeDeleteMany",
38
+ "afterDelete",
39
+ "afterDeleteMany",
40
+ "beforeCount",
41
+ "afterCount",
42
+ "beforeFindOne",
43
+ "afterFindOne",
44
+ "beforeFindMany",
45
+ "afterFindMany",
46
+ ];
27
47
  //# sourceMappingURL=constants.js.map
@@ -1,6 +1,10 @@
1
- import { StrapiQueryParams, StrapiQueryParamsParsed } from "strapi-typed";
1
+ import { StrapiQueryParams, StrapiQueryParamsParsed, StrapiContext } from "strapi-typed";
2
+ import { ContentType, LifeCycleEvent, LifeCycleHookName } from "./types";
3
+ import { Effect } from "../../types/utils";
2
4
  export declare const getPluginService: <T>(name: string) => T;
3
5
  export declare const parseParams: <T = StrapiQueryParamsParsed>(params: StrapiQueryParams) => T;
4
6
  export declare const assertParamsPresent: <T>(params: unknown, keys: string[]) => asserts params is T;
5
7
  export declare const assertNotEmpty: <T>(value: T | null | undefined, customError?: Error) => asserts value is T;
8
+ export declare const buildHookListener: (contentTypeName: ContentType, { strapi }: StrapiContext) => (hookName: LifeCycleHookName) => [LifeCycleHookName, Effect<LifeCycleEvent>];
9
+ export declare const buildAllHookListeners: (contentTypeName: ContentType, context: StrapiContext) => Record<LifeCycleHookName, Effect<LifeCycleEvent>>;
6
10
  //# sourceMappingURL=functions.d.ts.map
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.assertNotEmpty = exports.assertParamsPresent = exports.parseParams = exports.getPluginService = void 0;
6
+ exports.buildAllHookListeners = exports.buildHookListener = exports.assertNotEmpty = exports.assertParamsPresent = exports.parseParams = exports.getPluginService = void 0;
7
7
  const error_1 = __importDefault(require("./error"));
8
+ const constants_1 = require("./constants");
8
9
  const getPluginService = (name) => strapi.plugin("comments").service(name);
9
10
  exports.getPluginService = getPluginService;
10
11
  const parseParams = (params) => Object.keys(params).reduce((prev, curr) => {
@@ -33,4 +34,20 @@ const assertNotEmpty = (value, customError) => {
33
34
  throw (customError ?? new error_1.default(400, "Non-empty value expected, empty given"));
34
35
  };
35
36
  exports.assertNotEmpty = assertNotEmpty;
37
+ const buildHookListener = (contentTypeName, { strapi }) => (hookName) => [
38
+ hookName,
39
+ async (event) => {
40
+ const commonService = strapi
41
+ .plugin("comments")
42
+ .service("common");
43
+ await commonService.runLifecycleHook({
44
+ contentTypeName,
45
+ hookName,
46
+ event,
47
+ });
48
+ },
49
+ ];
50
+ exports.buildHookListener = buildHookListener;
51
+ const buildAllHookListeners = (contentTypeName, context) => Object.fromEntries(constants_1.LIFECYCLE_HOOKS.map((0, exports.buildHookListener)(contentTypeName, context)));
52
+ exports.buildAllHookListeners = buildAllHookListeners;
36
53
  //# sourceMappingURL=functions.js.map
@@ -0,0 +1,23 @@
1
+ import { Effect } from "../../types/utils";
2
+ export type ContentType = "comment" | "comment-report";
3
+ export type LifeCycleHookName = "beforeCreate" | "beforeCreateMany" | "afterCreate" | "afterCreateMany" | "beforeUpdate" | "beforeUpdateMany" | "afterUpdate" | "afterUpdateMany" | "beforeDelete" | "beforeDeleteMany" | "afterDelete" | "afterDeleteMany" | "beforeCount" | "afterCount" | "beforeFindOne" | "afterFindOne" | "beforeFindMany" | "afterFindMany";
4
+ export interface LifeCycleEvent<THookName extends LifeCycleHookName = LifeCycleHookName, TResult = unknown, TParams = Record<string, unknown>> {
5
+ action: THookName;
6
+ model: {
7
+ singularName: string;
8
+ uid: string;
9
+ tableName: string;
10
+ attributes: Record<string, unknown>;
11
+ lifecycles: Partial<Record<LifeCycleHookName, Effect<LifeCycleEvent>>>;
12
+ indexes: Array<{
13
+ type?: string;
14
+ name: string;
15
+ columns: string[];
16
+ }>;
17
+ columnToAttribute: Record<string, string>;
18
+ };
19
+ state: Record<string, unknown>;
20
+ params: TParams;
21
+ result?: TResult | TResult[];
22
+ }
23
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -95,6 +95,7 @@ declare const _default: () => {
95
95
  };
96
96
  };
97
97
  };
98
+ lifecycles: Record<import("./server/utils/types").LifeCycleHookName, import("./types/utils").Effect<import("./server/utils/types").LifeCycleEvent<import("./server/utils/types").LifeCycleHookName, unknown, Record<string, unknown>>>>;
98
99
  };
99
100
  "comment-report": {
100
101
  schema: {
@@ -143,6 +144,7 @@ declare const _default: () => {
143
144
  };
144
145
  };
145
146
  };
147
+ lifecycles: Record<import("./server/utils/types").LifeCycleHookName, import("./types/utils").Effect<import("./server/utils/types").LifeCycleEvent<import("./server/utils/types").LifeCycleHookName, unknown, Record<string, unknown>>>>;
146
148
  };
147
149
  };
148
150
  config: {