@strapi/content-releases 0.0.0-experimental.f7b9b47085e387e97f990d8695971b51d7f7149a → 0.0.0-next.37dd1e3ff22e1635b69683abadd444912ae0dbff

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.
@@ -1,4 +1,5 @@
1
1
  import { setCreatorFields, errors, validateYupSchema, yup as yup$1 } from "@strapi/utils";
2
+ import _ from "lodash/fp";
2
3
  import * as yup from "yup";
3
4
  const RELEASE_MODEL_UID = "plugin::content-releases.release";
4
5
  const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
@@ -46,10 +47,80 @@ const ACTIONS = [
46
47
  pluginName: "content-releases"
47
48
  }
48
49
  ];
49
- const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
50
+ const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
51
+ return strapi2.plugin("content-releases").service(name);
52
+ };
53
+ const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
50
54
  const register = async ({ strapi: strapi2 }) => {
51
- if (features$1.isEnabled("cms-content-releases") && strapi2.features.future.isEnabled("contentReleases")) {
55
+ if (features$2.isEnabled("cms-content-releases") && strapi2.features.future.isEnabled("contentReleases")) {
52
56
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
57
+ const releaseActionService = getService("release-action", { strapi: strapi2 });
58
+ const eventManager = getService("event-manager", { strapi: strapi2 });
59
+ const destroyContentTypeUpdateListener = strapi2.eventHub.on(
60
+ "content-type.update",
61
+ async ({ contentType }) => {
62
+ if (contentType.schema.options.draftAndPublish === false) {
63
+ await releaseActionService.deleteManyForContentType(contentType.uid);
64
+ }
65
+ }
66
+ );
67
+ eventManager.addDestroyListenerCallback(destroyContentTypeUpdateListener);
68
+ const destroyContentTypeDeleteListener = strapi2.eventHub.on(
69
+ "content-type.delete",
70
+ async ({ contentType }) => {
71
+ await releaseActionService.deleteManyForContentType(contentType.uid);
72
+ }
73
+ );
74
+ eventManager.addDestroyListenerCallback(destroyContentTypeDeleteListener);
75
+ }
76
+ };
77
+ const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
78
+ const bootstrap = async ({ strapi: strapi2 }) => {
79
+ if (features$1.isEnabled("cms-content-releases") && strapi2.features.future.isEnabled("contentReleases")) {
80
+ strapi2.db.lifecycles.subscribe({
81
+ afterDelete(event) {
82
+ const { model, result } = event;
83
+ if (model.kind === "collectionType" && model.options.draftAndPublish) {
84
+ const { id } = result;
85
+ strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
86
+ where: {
87
+ target_type: model.uid,
88
+ target_id: id
89
+ }
90
+ });
91
+ }
92
+ },
93
+ /**
94
+ * deleteMany hook doesn't return the deleted entries ids
95
+ * so we need to fetch them before deleting the entries to save the ids on our state
96
+ */
97
+ async beforeDeleteMany(event) {
98
+ const { model, params } = event;
99
+ if (model.kind === "collectionType" && model.options.draftAndPublish) {
100
+ const { where } = params;
101
+ const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
102
+ event.state.entriesToDelete = entriesToDelete;
103
+ }
104
+ },
105
+ /**
106
+ * We delete the release actions related to deleted entries
107
+ * We make this only after deleteMany is succesfully executed to avoid errors
108
+ */
109
+ async afterDeleteMany(event) {
110
+ const { model, state } = event;
111
+ const entriesToDelete = state.entriesToDelete;
112
+ if (entriesToDelete) {
113
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
114
+ where: {
115
+ target_type: model.uid,
116
+ target_id: {
117
+ $in: entriesToDelete.map((entry) => entry.id)
118
+ }
119
+ }
120
+ });
121
+ }
122
+ }
123
+ });
53
124
  }
54
125
  };
55
126
  const schema$1 = {
@@ -122,6 +193,9 @@ const schema = {
122
193
  type: "string",
123
194
  required: true
124
195
  },
196
+ locale: {
197
+ type: "string"
198
+ },
125
199
  release: {
126
200
  type: "relation",
127
201
  relation: "manyToOne",
@@ -137,8 +211,26 @@ const contentTypes = {
137
211
  release: release$1,
138
212
  "release-action": releaseAction$1
139
213
  };
140
- const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
141
- return strapi2.plugin("content-releases").service(name);
214
+ const createReleaseActionService = ({ strapi: strapi2 }) => ({
215
+ async deleteManyForContentType(contentTypeUid) {
216
+ return strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
217
+ where: {
218
+ target_type: contentTypeUid
219
+ }
220
+ });
221
+ }
222
+ });
223
+ const getGroupName = (queryValue) => {
224
+ switch (queryValue) {
225
+ case "contentType":
226
+ return "entry.contentType.displayName";
227
+ case "action":
228
+ return "type";
229
+ case "locale":
230
+ return _.getOr("No locale", "entry.locale.name");
231
+ default:
232
+ return "entry.contentType.displayName";
233
+ }
142
234
  };
143
235
  const createReleaseService = ({ strapi: strapi2 }) => ({
144
236
  async create(releaseData, { user }) {
@@ -224,19 +316,23 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
224
316
  });
225
317
  },
226
318
  async update(id, releaseData, { user }) {
227
- const updatedRelease = await setCreatorFields({ user, isEdition: true })(releaseData);
228
- const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
319
+ const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(releaseData);
320
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
321
+ if (!release2) {
322
+ throw new errors.NotFoundError(`No release found for id ${id}`);
323
+ }
324
+ if (release2.releasedAt) {
325
+ throw new errors.ValidationError("Release already published");
326
+ }
327
+ const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
229
328
  /*
230
329
  * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
231
330
  * is not compatible with the type we are passing here: UpdateRelease.Request['body']
232
331
  */
233
332
  // @ts-expect-error see above
234
- data: updatedRelease
333
+ data: releaseWithCreatorFields
235
334
  });
236
- if (!release2) {
237
- throw new errors.NotFoundError(`No release found for id ${id}`);
238
- }
239
- return release2;
335
+ return updatedRelease;
240
336
  },
241
337
  async createAction(releaseId, action) {
242
338
  const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
@@ -246,11 +342,19 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
246
342
  validateEntryContentType(action.entry.contentType),
247
343
  validateUniqueEntry(releaseId, action)
248
344
  ]);
345
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
346
+ if (!release2) {
347
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
348
+ }
349
+ if (release2.releasedAt) {
350
+ throw new errors.ValidationError("Release already published");
351
+ }
249
352
  const { entry, type } = action;
250
353
  return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
251
354
  data: {
252
355
  type,
253
356
  contentType: entry.contentType,
357
+ locale: entry.locale,
254
358
  entry: {
255
359
  id: entry.id,
256
360
  __type: entry.contentType,
@@ -262,8 +366,10 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
262
366
  });
263
367
  },
264
368
  async findActions(releaseId, query) {
265
- const result = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
266
- if (!result) {
369
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
370
+ fields: ["id"]
371
+ });
372
+ if (!release2) {
267
373
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
268
374
  }
269
375
  return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
@@ -279,18 +385,40 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
279
385
  async countActions(query) {
280
386
  return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
281
387
  },
282
- async getAllContentTypeUids(releaseId) {
283
- const contentTypesFromReleaseActions = await strapi2.db.queryBuilder(RELEASE_ACTION_MODEL_UID).select("content_type").where({
284
- $and: [
285
- {
286
- release: releaseId
388
+ async groupActions(actions, groupBy) {
389
+ const contentTypeUids = actions.reduce((acc, action) => {
390
+ if (!acc.includes(action.contentType)) {
391
+ acc.push(action.contentType);
392
+ }
393
+ return acc;
394
+ }, []);
395
+ const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
396
+ contentTypeUids
397
+ );
398
+ const allLocales = await strapi2.plugin("i18n").service("locales").find();
399
+ const allLocalesDictionary = allLocales.reduce((acc, locale) => {
400
+ acc[locale.code] = { name: locale.name, code: locale.code };
401
+ return acc;
402
+ }, {});
403
+ const formattedData = actions.map((action) => {
404
+ const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
405
+ return {
406
+ ...action,
407
+ entry: {
408
+ id: action.entry.id,
409
+ contentType: {
410
+ displayName,
411
+ mainFieldValue: action.entry[mainField]
412
+ },
413
+ locale: action.locale ? allLocalesDictionary[action.locale] : null,
414
+ status: action.entry.publishedAt ? "published" : "draft"
287
415
  }
288
- ]
289
- }).groupBy("content_type").execute();
290
- return contentTypesFromReleaseActions.map(({ contentType: contentTypeUid }) => contentTypeUid);
416
+ };
417
+ });
418
+ const groupName = getGroupName(groupBy);
419
+ return _.groupBy(groupName)(formattedData);
291
420
  },
292
- async getContentTypesDataForActions(releaseId) {
293
- const contentTypesUids = await this.getAllContentTypeUids(releaseId);
421
+ async getContentTypesDataForActions(contentTypesUids) {
294
422
  const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
295
423
  const contentTypesData = {};
296
424
  for (const contentTypeUid of contentTypesUids) {
@@ -395,13 +523,18 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
395
523
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
396
524
  where: {
397
525
  id: actionId,
398
- release: releaseId
526
+ release: {
527
+ id: releaseId,
528
+ releasedAt: {
529
+ $null: true
530
+ }
531
+ }
399
532
  },
400
533
  data: update
401
534
  });
402
535
  if (!updatedAction) {
403
536
  throw new errors.NotFoundError(
404
- `Action with id ${actionId} not found in release with id ${releaseId}`
537
+ `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
405
538
  );
406
539
  }
407
540
  return updatedAction;
@@ -410,12 +543,17 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
410
543
  const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
411
544
  where: {
412
545
  id: actionId,
413
- release: releaseId
546
+ release: {
547
+ id: releaseId,
548
+ releasedAt: {
549
+ $null: true
550
+ }
551
+ }
414
552
  }
415
553
  });
416
554
  if (!deletedAction) {
417
555
  throw new errors.NotFoundError(
418
- `Action with id ${actionId} not found in release with id ${releaseId}`
556
+ `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
419
557
  );
420
558
  }
421
559
  return deletedAction;
@@ -450,7 +588,30 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
450
588
  }
451
589
  }
452
590
  });
453
- const services = { release: createReleaseService, "release-validation": createReleaseValidationService };
591
+ const createEventManagerService = () => {
592
+ const state = {
593
+ destroyListenerCallbacks: []
594
+ };
595
+ return {
596
+ addDestroyListenerCallback(destroyListenerCallback) {
597
+ state.destroyListenerCallbacks.push(destroyListenerCallback);
598
+ },
599
+ destroyAllListeners() {
600
+ if (!state.destroyListenerCallbacks.length) {
601
+ return;
602
+ }
603
+ state.destroyListenerCallbacks.forEach((destroyListenerCallback) => {
604
+ destroyListenerCallback();
605
+ });
606
+ }
607
+ };
608
+ };
609
+ const services = {
610
+ release: createReleaseService,
611
+ "release-action": createReleaseActionService,
612
+ "release-validation": createReleaseValidationService,
613
+ "event-manager": createEventManagerService
614
+ };
454
615
  const RELEASE_SCHEMA = yup.object().shape({
455
616
  name: yup.string().trim().required()
456
617
  }).required().noUnknown();
@@ -595,31 +756,13 @@ const releaseActionController = {
595
756
  });
596
757
  const query = await permissionsManager.sanitizeQuery(ctx.query);
597
758
  const releaseService = getService("release", { strapi });
598
- const { results, pagination } = await releaseService.findActions(releaseId, query);
599
- const allReleaseContentTypesDictionary = await releaseService.getContentTypesDataForActions(
600
- releaseId
601
- );
602
- const allLocales = await strapi.plugin("i18n").service("locales").find();
603
- const allLocalesDictionary = allLocales.reduce((acc, locale) => {
604
- acc[locale.code] = { name: locale.name, code: locale.code };
605
- return acc;
606
- }, {});
607
- const data = results.map((action) => {
608
- const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
609
- return {
610
- ...action,
611
- entry: {
612
- id: action.entry.id,
613
- contentType: {
614
- displayName,
615
- mainFieldValue: action.entry[mainField]
616
- },
617
- locale: allLocalesDictionary[action.entry.locale]
618
- }
619
- };
759
+ const { results, pagination } = await releaseService.findActions(releaseId, {
760
+ sort: query.groupBy === "action" ? "type" : query.groupBy,
761
+ ...query
620
762
  });
763
+ const groupedData = await releaseService.groupActions(results, query.groupBy);
621
764
  ctx.body = {
622
- data,
765
+ data: groupedData,
623
766
  meta: {
624
767
  pagination
625
768
  }
@@ -643,10 +786,8 @@ const releaseActionController = {
643
786
  async delete(ctx) {
644
787
  const actionId = ctx.params.actionId;
645
788
  const releaseId = ctx.params.releaseId;
646
- const deletedReleaseAction = await getService("release", { strapi }).deleteAction(
647
- actionId,
648
- releaseId
649
- );
789
+ const releaseService = getService("release", { strapi });
790
+ const deletedReleaseAction = await releaseService.deleteAction(actionId, releaseId);
650
791
  ctx.body = {
651
792
  data: deletedReleaseAction
652
793
  };
@@ -832,10 +973,16 @@ const getPlugin = () => {
832
973
  if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
833
974
  return {
834
975
  register,
976
+ bootstrap,
835
977
  contentTypes,
836
978
  services,
837
979
  controllers,
838
- routes
980
+ routes,
981
+ destroy() {
982
+ if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
983
+ getService("event-manager").destroyAllListeners();
984
+ }
985
+ }
839
986
  };
840
987
  }
841
988
  return {