@strapi/content-releases 0.0.0-next.90a86f595c31de9a89f4255318bfb0cccb30ceed → 0.0.0-next.a9d79bec775daaf0da4e506b2aebafdb4ca95b06

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 (29) hide show
  1. package/dist/_chunks/{App-8FCxPK8-.mjs → App-bpzO2Ljh.mjs} +616 -365
  2. package/dist/_chunks/App-bpzO2Ljh.mjs.map +1 -0
  3. package/dist/_chunks/{App-pspKUC-W.js → App-p8aKBitd.js} +606 -354
  4. package/dist/_chunks/App-p8aKBitd.js.map +1 -0
  5. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs +51 -0
  6. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +1 -0
  7. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js +51 -0
  8. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +1 -0
  9. package/dist/_chunks/{en-m9eTk4UF.mjs → en-WuuhP6Bn.mjs} +15 -4
  10. package/dist/_chunks/en-WuuhP6Bn.mjs.map +1 -0
  11. package/dist/_chunks/{en-r9YocBH0.js → en-gcJJ5htG.js} +15 -4
  12. package/dist/_chunks/en-gcJJ5htG.js.map +1 -0
  13. package/dist/_chunks/{index-8aK7GzI5.mjs → index-AECgcaDa.mjs} +99 -22
  14. package/dist/_chunks/index-AECgcaDa.mjs.map +1 -0
  15. package/dist/_chunks/{index-nGaPcY9m.js → index-fP3qoWZ4.js} +88 -11
  16. package/dist/_chunks/index-fP3qoWZ4.js.map +1 -0
  17. package/dist/admin/index.js +1 -1
  18. package/dist/admin/index.mjs +2 -2
  19. package/dist/server/index.js +818 -432
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +817 -432
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +13 -11
  24. package/dist/_chunks/App-8FCxPK8-.mjs.map +0 -1
  25. package/dist/_chunks/App-pspKUC-W.js.map +0 -1
  26. package/dist/_chunks/en-m9eTk4UF.mjs.map +0 -1
  27. package/dist/_chunks/en-r9YocBH0.js.map +0 -1
  28. package/dist/_chunks/index-8aK7GzI5.mjs.map +0 -1
  29. package/dist/_chunks/index-nGaPcY9m.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { contentTypes as contentTypes$1, mapAsync, setCreatorFields, errors, validateYupSchema, yup as yup$1 } from "@strapi/utils";
2
+ import isEqual from "lodash/isEqual";
2
3
  import { difference, keys } from "lodash";
3
4
  import _ from "lodash/fp";
4
5
  import EE from "@strapi/strapi/dist/utils/ee";
@@ -50,6 +51,32 @@ const ACTIONS = [
50
51
  pluginName: "content-releases"
51
52
  }
52
53
  ];
54
+ const ALLOWED_WEBHOOK_EVENTS = {
55
+ RELEASES_PUBLISH: "releases.publish"
56
+ };
57
+ const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
58
+ return strapi2.plugin("content-releases").service(name);
59
+ };
60
+ const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
61
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
62
+ const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
63
+ const entry = await strapi2.entityService.findOne(contentTypeUid, entryId, { populate });
64
+ return entry;
65
+ };
66
+ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
67
+ try {
68
+ await strapi2.entityValidator.validateEntityCreation(
69
+ strapi2.getModel(contentTypeUid),
70
+ entry,
71
+ void 0,
72
+ // @ts-expect-error - FIXME: entity here is unnecessary
73
+ entry
74
+ );
75
+ return true;
76
+ } catch {
77
+ return false;
78
+ }
79
+ };
53
80
  async function deleteActionsOnDisableDraftAndPublish({
54
81
  oldContentTypes,
55
82
  contentTypes: contentTypes2
@@ -76,31 +103,196 @@ async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes:
76
103
  });
77
104
  }
78
105
  }
106
+ async function migrateIsValidAndStatusReleases() {
107
+ const releasesWithoutStatus = await strapi.db.query(RELEASE_MODEL_UID).findMany({
108
+ where: {
109
+ status: null,
110
+ releasedAt: null
111
+ },
112
+ populate: {
113
+ actions: {
114
+ populate: {
115
+ entry: true
116
+ }
117
+ }
118
+ }
119
+ });
120
+ mapAsync(releasesWithoutStatus, async (release2) => {
121
+ const actions = release2.actions;
122
+ const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
123
+ for (const action of notValidatedActions) {
124
+ if (action.entry) {
125
+ const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
126
+ strapi
127
+ });
128
+ if (populatedEntry) {
129
+ const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
130
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
131
+ where: {
132
+ id: action.id
133
+ },
134
+ data: {
135
+ isEntryValid
136
+ }
137
+ });
138
+ }
139
+ }
140
+ }
141
+ return getService("release", { strapi }).updateReleaseStatus(release2.id);
142
+ });
143
+ const publishedReleases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
144
+ where: {
145
+ status: null,
146
+ releasedAt: {
147
+ $notNull: true
148
+ }
149
+ }
150
+ });
151
+ mapAsync(publishedReleases, async (release2) => {
152
+ return strapi.db.query(RELEASE_MODEL_UID).update({
153
+ where: {
154
+ id: release2.id
155
+ },
156
+ data: {
157
+ status: "done"
158
+ }
159
+ });
160
+ });
161
+ }
162
+ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
163
+ if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
164
+ const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
165
+ (uid) => oldContentTypes[uid]?.options?.draftAndPublish
166
+ );
167
+ const releasesAffected = /* @__PURE__ */ new Set();
168
+ mapAsync(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
169
+ const oldContentType = oldContentTypes[contentTypeUID];
170
+ const contentType = contentTypes2[contentTypeUID];
171
+ if (!isEqual(oldContentType?.attributes, contentType?.attributes)) {
172
+ const actions = await strapi.db.query(RELEASE_ACTION_MODEL_UID).findMany({
173
+ where: {
174
+ contentType: contentTypeUID
175
+ },
176
+ populate: {
177
+ entry: true,
178
+ release: true
179
+ }
180
+ });
181
+ await mapAsync(actions, async (action) => {
182
+ if (action.entry && action.release) {
183
+ const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
184
+ strapi
185
+ });
186
+ if (populatedEntry) {
187
+ const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
188
+ strapi
189
+ });
190
+ releasesAffected.add(action.release.id);
191
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
192
+ where: {
193
+ id: action.id
194
+ },
195
+ data: {
196
+ isEntryValid
197
+ }
198
+ });
199
+ }
200
+ }
201
+ });
202
+ }
203
+ }).then(() => {
204
+ mapAsync(releasesAffected, async (releaseId) => {
205
+ return getService("release", { strapi }).updateReleaseStatus(releaseId);
206
+ });
207
+ });
208
+ }
209
+ }
210
+ async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
211
+ if (!oldContentTypes) {
212
+ return;
213
+ }
214
+ for (const uid in contentTypes2) {
215
+ if (!oldContentTypes[uid]) {
216
+ continue;
217
+ }
218
+ const oldContentType = oldContentTypes[uid];
219
+ const contentType = contentTypes2[uid];
220
+ const i18nPlugin = strapi.plugin("i18n");
221
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
222
+ if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
223
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
224
+ locale: null
225
+ }).where({ contentType: uid }).execute();
226
+ }
227
+ }
228
+ }
229
+ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
230
+ if (!oldContentTypes) {
231
+ return;
232
+ }
233
+ for (const uid in contentTypes2) {
234
+ if (!oldContentTypes[uid]) {
235
+ continue;
236
+ }
237
+ const oldContentType = oldContentTypes[uid];
238
+ const contentType = contentTypes2[uid];
239
+ const i18nPlugin = strapi.plugin("i18n");
240
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
241
+ const { getDefaultLocale } = i18nPlugin.service("locales");
242
+ if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
243
+ const defaultLocale = await getDefaultLocale();
244
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
245
+ locale: defaultLocale
246
+ }).where({ contentType: uid }).execute();
247
+ }
248
+ }
249
+ }
79
250
  const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
80
251
  const register = async ({ strapi: strapi2 }) => {
81
252
  if (features$2.isEnabled("cms-content-releases")) {
82
253
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
83
- strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
84
- strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
254
+ strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish).register(disableContentTypeLocalized);
255
+ strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
256
+ }
257
+ if (strapi2.plugin("graphql")) {
258
+ const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
259
+ graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
260
+ graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
85
261
  }
86
- };
87
- const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
88
- return strapi2.plugin("content-releases").service(name);
89
262
  };
90
263
  const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
91
264
  const bootstrap = async ({ strapi: strapi2 }) => {
92
265
  if (features$1.isEnabled("cms-content-releases")) {
266
+ const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
267
+ (uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
268
+ );
93
269
  strapi2.db.lifecycles.subscribe({
94
- afterDelete(event) {
95
- const { model, result } = event;
96
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
97
- const { id } = result;
98
- strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
99
- where: {
100
- target_type: model.uid,
101
- target_id: id
270
+ models: contentTypesWithDraftAndPublish,
271
+ async afterDelete(event) {
272
+ try {
273
+ const { model, result } = event;
274
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
275
+ const { id } = result;
276
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
277
+ where: {
278
+ actions: {
279
+ target_type: model.uid,
280
+ target_id: id
281
+ }
282
+ }
283
+ });
284
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
285
+ where: {
286
+ target_type: model.uid,
287
+ target_id: id
288
+ }
289
+ });
290
+ for (const release2 of releases) {
291
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
102
292
  }
103
- });
293
+ }
294
+ } catch (error) {
295
+ strapi2.log.error("Error while deleting release actions after entry delete", { error });
104
296
  }
105
297
  },
106
298
  /**
@@ -120,18 +312,75 @@ const bootstrap = async ({ strapi: strapi2 }) => {
120
312
  * We make this only after deleteMany is succesfully executed to avoid errors
121
313
  */
122
314
  async afterDeleteMany(event) {
123
- const { model, state } = event;
124
- const entriesToDelete = state.entriesToDelete;
125
- if (entriesToDelete) {
126
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
127
- where: {
128
- target_type: model.uid,
129
- target_id: {
130
- $in: entriesToDelete.map((entry) => entry.id)
315
+ try {
316
+ const { model, state } = event;
317
+ const entriesToDelete = state.entriesToDelete;
318
+ if (entriesToDelete) {
319
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
320
+ where: {
321
+ actions: {
322
+ target_type: model.uid,
323
+ target_id: {
324
+ $in: entriesToDelete.map(
325
+ (entry) => entry.id
326
+ )
327
+ }
328
+ }
131
329
  }
330
+ });
331
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
332
+ where: {
333
+ target_type: model.uid,
334
+ target_id: {
335
+ $in: entriesToDelete.map((entry) => entry.id)
336
+ }
337
+ }
338
+ });
339
+ for (const release2 of releases) {
340
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
132
341
  }
342
+ }
343
+ } catch (error) {
344
+ strapi2.log.error("Error while deleting release actions after entry deleteMany", {
345
+ error
133
346
  });
134
347
  }
348
+ },
349
+ async afterUpdate(event) {
350
+ try {
351
+ const { model, result } = event;
352
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
353
+ const isEntryValid = await getEntryValidStatus(
354
+ model.uid,
355
+ result,
356
+ {
357
+ strapi: strapi2
358
+ }
359
+ );
360
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
361
+ where: {
362
+ target_type: model.uid,
363
+ target_id: result.id
364
+ },
365
+ data: {
366
+ isEntryValid
367
+ }
368
+ });
369
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
370
+ where: {
371
+ actions: {
372
+ target_type: model.uid,
373
+ target_id: result.id
374
+ }
375
+ }
376
+ });
377
+ for (const release2 of releases) {
378
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
379
+ }
380
+ }
381
+ } catch (error) {
382
+ strapi2.log.error("Error while updating release actions after entry update", { error });
383
+ }
135
384
  }
136
385
  });
137
386
  if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
@@ -141,6 +390,9 @@ const bootstrap = async ({ strapi: strapi2 }) => {
141
390
  );
142
391
  throw err;
143
392
  });
393
+ Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
394
+ strapi2.webhookStore.addAllowedEvent(key, value);
395
+ });
144
396
  }
145
397
  }
146
398
  };
@@ -183,6 +435,14 @@ const schema$1 = {
183
435
  scheduledAt: {
184
436
  type: "datetime"
185
437
  },
438
+ timezone: {
439
+ type: "string"
440
+ },
441
+ status: {
442
+ type: "enumeration",
443
+ enum: ["ready", "blocked", "failed", "done", "empty"],
444
+ required: true
445
+ },
186
446
  actions: {
187
447
  type: "relation",
188
448
  relation: "oneToMany",
@@ -235,6 +495,9 @@ const schema = {
235
495
  relation: "manyToOne",
236
496
  target: RELEASE_MODEL_UID,
237
497
  inversedBy: "actions"
498
+ },
499
+ isEntryValid: {
500
+ type: "boolean"
238
501
  }
239
502
  }
240
503
  };
@@ -257,464 +520,563 @@ const getGroupName = (queryValue) => {
257
520
  return "contentType.displayName";
258
521
  }
259
522
  };
260
- const createReleaseService = ({ strapi: strapi2 }) => ({
261
- async create(releaseData, { user }) {
262
- const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
263
- const {
264
- validatePendingReleasesLimit,
265
- validateUniqueNameForPendingRelease,
266
- validateScheduledAtIsLaterThanNow
267
- } = getService("release-validation", { strapi: strapi2 });
268
- await Promise.all([
269
- validatePendingReleasesLimit(),
270
- validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
271
- validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
272
- ]);
273
- const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
274
- data: releaseWithCreatorFields
523
+ const createReleaseService = ({ strapi: strapi2 }) => {
524
+ const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
525
+ strapi2.eventHub.emit(event, {
526
+ isPublished,
527
+ error,
528
+ release: release2
275
529
  });
276
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
277
- const schedulingService = getService("scheduling", { strapi: strapi2 });
278
- await schedulingService.set(release2.id, release2.scheduledAt);
279
- }
280
- return release2;
281
- },
282
- async findOne(id, query = {}) {
283
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
284
- ...query
285
- });
286
- return release2;
287
- },
288
- findPage(query) {
289
- return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
290
- ...query,
291
- populate: {
292
- actions: {
293
- // @ts-expect-error Ignore missing properties
294
- count: true
530
+ };
531
+ return {
532
+ async create(releaseData, { user }) {
533
+ const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
534
+ const {
535
+ validatePendingReleasesLimit,
536
+ validateUniqueNameForPendingRelease,
537
+ validateScheduledAtIsLaterThanNow
538
+ } = getService("release-validation", { strapi: strapi2 });
539
+ await Promise.all([
540
+ validatePendingReleasesLimit(),
541
+ validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
542
+ validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
543
+ ]);
544
+ const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
545
+ data: {
546
+ ...releaseWithCreatorFields,
547
+ status: "empty"
295
548
  }
549
+ });
550
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
551
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
552
+ await schedulingService.set(release2.id, release2.scheduledAt);
296
553
  }
297
- });
298
- },
299
- async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
300
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
301
- where: {
302
- actions: {
303
- target_type: contentTypeUid,
304
- target_id: entryId
554
+ strapi2.telemetry.send("didCreateContentRelease");
555
+ return release2;
556
+ },
557
+ async findOne(id, query = {}) {
558
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
559
+ ...query
560
+ });
561
+ return release2;
562
+ },
563
+ findPage(query) {
564
+ return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
565
+ ...query,
566
+ populate: {
567
+ actions: {
568
+ // @ts-expect-error Ignore missing properties
569
+ count: true
570
+ }
571
+ }
572
+ });
573
+ },
574
+ async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
575
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
576
+ where: {
577
+ actions: {
578
+ target_type: contentTypeUid,
579
+ target_id: entryId
580
+ },
581
+ releasedAt: {
582
+ $null: true
583
+ }
305
584
  },
306
- releasedAt: {
307
- $null: true
585
+ populate: {
586
+ // Filter the action to get only the content type entry
587
+ actions: {
588
+ where: {
589
+ target_type: contentTypeUid,
590
+ target_id: entryId
591
+ }
592
+ }
308
593
  }
309
- },
310
- populate: {
311
- // Filter the action to get only the content type entry
312
- actions: {
313
- where: {
594
+ });
595
+ return releases.map((release2) => {
596
+ if (release2.actions?.length) {
597
+ const [actionForEntry] = release2.actions;
598
+ delete release2.actions;
599
+ return {
600
+ ...release2,
601
+ action: actionForEntry
602
+ };
603
+ }
604
+ return release2;
605
+ });
606
+ },
607
+ async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
608
+ const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
609
+ where: {
610
+ releasedAt: {
611
+ $null: true
612
+ },
613
+ actions: {
314
614
  target_type: contentTypeUid,
315
615
  target_id: entryId
316
616
  }
317
617
  }
618
+ });
619
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
620
+ where: {
621
+ $or: [
622
+ {
623
+ id: {
624
+ $notIn: releasesRelated.map((release2) => release2.id)
625
+ }
626
+ },
627
+ {
628
+ actions: null
629
+ }
630
+ ],
631
+ releasedAt: {
632
+ $null: true
633
+ }
634
+ }
635
+ });
636
+ return releases.map((release2) => {
637
+ if (release2.actions?.length) {
638
+ const [actionForEntry] = release2.actions;
639
+ delete release2.actions;
640
+ return {
641
+ ...release2,
642
+ action: actionForEntry
643
+ };
644
+ }
645
+ return release2;
646
+ });
647
+ },
648
+ async update(id, releaseData, { user }) {
649
+ const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(
650
+ releaseData
651
+ );
652
+ const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
653
+ "release-validation",
654
+ { strapi: strapi2 }
655
+ );
656
+ await Promise.all([
657
+ validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
658
+ validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
659
+ ]);
660
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
661
+ if (!release2) {
662
+ throw new errors.NotFoundError(`No release found for id ${id}`);
318
663
  }
319
- });
320
- return releases.map((release2) => {
321
- if (release2.actions?.length) {
322
- const [actionForEntry] = release2.actions;
323
- delete release2.actions;
324
- return {
325
- ...release2,
326
- action: actionForEntry
327
- };
664
+ if (release2.releasedAt) {
665
+ throw new errors.ValidationError("Release already published");
328
666
  }
329
- return release2;
330
- });
331
- },
332
- async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
333
- const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
334
- where: {
335
- releasedAt: {
336
- $null: true
337
- },
338
- actions: {
339
- target_type: contentTypeUid,
340
- target_id: entryId
667
+ const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
668
+ /*
669
+ * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
670
+ * is not compatible with the type we are passing here: UpdateRelease.Request['body']
671
+ */
672
+ // @ts-expect-error see above
673
+ data: releaseWithCreatorFields
674
+ });
675
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
676
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
677
+ if (releaseData.scheduledAt) {
678
+ await schedulingService.set(id, releaseData.scheduledAt);
679
+ } else if (release2.scheduledAt) {
680
+ schedulingService.cancel(id);
341
681
  }
342
682
  }
343
- });
344
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
345
- where: {
346
- $or: [
347
- {
348
- id: {
349
- $notIn: releasesRelated.map((release2) => release2.id)
350
- }
351
- },
352
- {
353
- actions: null
354
- }
355
- ],
356
- releasedAt: {
357
- $null: true
358
- }
683
+ this.updateReleaseStatus(id);
684
+ strapi2.telemetry.send("didUpdateContentRelease");
685
+ return updatedRelease;
686
+ },
687
+ async createAction(releaseId, action) {
688
+ const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
689
+ strapi: strapi2
690
+ });
691
+ await Promise.all([
692
+ validateEntryContentType(action.entry.contentType),
693
+ validateUniqueEntry(releaseId, action)
694
+ ]);
695
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
696
+ if (!release2) {
697
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
359
698
  }
360
- });
361
- return releases.map((release2) => {
362
- if (release2.actions?.length) {
363
- const [actionForEntry] = release2.actions;
364
- delete release2.actions;
365
- return {
366
- ...release2,
367
- action: actionForEntry
368
- };
699
+ if (release2.releasedAt) {
700
+ throw new errors.ValidationError("Release already published");
369
701
  }
370
- return release2;
371
- });
372
- },
373
- async update(id, releaseData, { user }) {
374
- const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(releaseData);
375
- const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
376
- "release-validation",
377
- { strapi: strapi2 }
378
- );
379
- await Promise.all([
380
- validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
381
- validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
382
- ]);
383
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
384
- if (!release2) {
385
- throw new errors.NotFoundError(`No release found for id ${id}`);
386
- }
387
- if (release2.releasedAt) {
388
- throw new errors.ValidationError("Release already published");
389
- }
390
- const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
391
- /*
392
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
393
- * is not compatible with the type we are passing here: UpdateRelease.Request['body']
394
- */
395
- // @ts-expect-error see above
396
- data: releaseWithCreatorFields
397
- });
398
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
399
- const schedulingService = getService("scheduling", { strapi: strapi2 });
400
- if (releaseData.scheduledAt) {
401
- await schedulingService.set(id, releaseData.scheduledAt);
402
- } else if (release2.scheduledAt) {
403
- schedulingService.cancel(id);
702
+ const { entry, type } = action;
703
+ const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
704
+ const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
705
+ const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
706
+ data: {
707
+ type,
708
+ contentType: entry.contentType,
709
+ locale: entry.locale,
710
+ isEntryValid,
711
+ entry: {
712
+ id: entry.id,
713
+ __type: entry.contentType,
714
+ __pivot: { field: "entry" }
715
+ },
716
+ release: releaseId
717
+ },
718
+ populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
719
+ });
720
+ this.updateReleaseStatus(releaseId);
721
+ return releaseAction2;
722
+ },
723
+ async findActions(releaseId, query) {
724
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
725
+ fields: ["id"]
726
+ });
727
+ if (!release2) {
728
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
404
729
  }
405
- }
406
- return updatedRelease;
407
- },
408
- async createAction(releaseId, action) {
409
- const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
410
- strapi: strapi2
411
- });
412
- await Promise.all([
413
- validateEntryContentType(action.entry.contentType),
414
- validateUniqueEntry(releaseId, action)
415
- ]);
416
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
417
- if (!release2) {
418
- throw new errors.NotFoundError(`No release found for id ${releaseId}`);
419
- }
420
- if (release2.releasedAt) {
421
- throw new errors.ValidationError("Release already published");
422
- }
423
- const { entry, type } = action;
424
- return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
425
- data: {
426
- type,
427
- contentType: entry.contentType,
428
- locale: entry.locale,
429
- entry: {
430
- id: entry.id,
431
- __type: entry.contentType,
432
- __pivot: { field: "entry" }
730
+ return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
731
+ ...query,
732
+ populate: {
733
+ entry: {
734
+ populate: "*"
735
+ }
433
736
  },
434
- release: releaseId
435
- },
436
- populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
437
- });
438
- },
439
- async findActions(releaseId, query) {
440
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
441
- fields: ["id"]
442
- });
443
- if (!release2) {
444
- throw new errors.NotFoundError(`No release found for id ${releaseId}`);
445
- }
446
- return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
447
- ...query,
448
- populate: {
449
- entry: {
450
- populate: "*"
737
+ filters: {
738
+ release: releaseId
451
739
  }
452
- },
453
- filters: {
454
- release: releaseId
455
- }
456
- });
457
- },
458
- async countActions(query) {
459
- return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
460
- },
461
- async groupActions(actions, groupBy) {
462
- const contentTypeUids = actions.reduce((acc, action) => {
463
- if (!acc.includes(action.contentType)) {
464
- acc.push(action.contentType);
465
- }
466
- return acc;
467
- }, []);
468
- const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
469
- contentTypeUids
470
- );
471
- const allLocalesDictionary = await this.getLocalesDataForActions();
472
- const formattedData = actions.map((action) => {
473
- const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
474
- return {
475
- ...action,
476
- locale: action.locale ? allLocalesDictionary[action.locale] : null,
477
- contentType: {
478
- displayName,
479
- mainFieldValue: action.entry[mainField],
480
- uid: action.contentType
740
+ });
741
+ },
742
+ async countActions(query) {
743
+ return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
744
+ },
745
+ async groupActions(actions, groupBy) {
746
+ const contentTypeUids = actions.reduce((acc, action) => {
747
+ if (!acc.includes(action.contentType)) {
748
+ acc.push(action.contentType);
481
749
  }
482
- };
483
- });
484
- const groupName = getGroupName(groupBy);
485
- return _.groupBy(groupName)(formattedData);
486
- },
487
- async getLocalesDataForActions() {
488
- if (!strapi2.plugin("i18n")) {
489
- return {};
490
- }
491
- const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
492
- return allLocales.reduce((acc, locale) => {
493
- acc[locale.code] = { name: locale.name, code: locale.code };
494
- return acc;
495
- }, {});
496
- },
497
- async getContentTypesDataForActions(contentTypesUids) {
498
- const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
499
- const contentTypesData = {};
500
- for (const contentTypeUid of contentTypesUids) {
501
- const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
502
- uid: contentTypeUid
750
+ return acc;
751
+ }, []);
752
+ const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
753
+ contentTypeUids
754
+ );
755
+ const allLocalesDictionary = await this.getLocalesDataForActions();
756
+ const formattedData = actions.map((action) => {
757
+ const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
758
+ return {
759
+ ...action,
760
+ locale: action.locale ? allLocalesDictionary[action.locale] : null,
761
+ contentType: {
762
+ displayName,
763
+ mainFieldValue: action.entry[mainField],
764
+ uid: action.contentType
765
+ }
766
+ };
503
767
  });
504
- contentTypesData[contentTypeUid] = {
505
- mainField: contentTypeConfig.settings.mainField,
506
- displayName: strapi2.getModel(contentTypeUid).info.displayName
507
- };
508
- }
509
- return contentTypesData;
510
- },
511
- getContentTypeModelsFromActions(actions) {
512
- const contentTypeUids = actions.reduce((acc, action) => {
513
- if (!acc.includes(action.contentType)) {
514
- acc.push(action.contentType);
768
+ const groupName = getGroupName(groupBy);
769
+ return _.groupBy(groupName)(formattedData);
770
+ },
771
+ async getLocalesDataForActions() {
772
+ if (!strapi2.plugin("i18n")) {
773
+ return {};
515
774
  }
516
- return acc;
517
- }, []);
518
- const contentTypeModelsMap = contentTypeUids.reduce(
519
- (acc, contentTypeUid) => {
520
- acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
775
+ const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
776
+ return allLocales.reduce((acc, locale) => {
777
+ acc[locale.code] = { name: locale.name, code: locale.code };
521
778
  return acc;
522
- },
523
- {}
524
- );
525
- return contentTypeModelsMap;
526
- },
527
- async getAllComponents() {
528
- const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
529
- const components = await contentManagerComponentsService.findAllComponents();
530
- const componentsMap = components.reduce(
531
- (acc, component) => {
532
- acc[component.uid] = component;
779
+ }, {});
780
+ },
781
+ async getContentTypesDataForActions(contentTypesUids) {
782
+ const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
783
+ const contentTypesData = {};
784
+ for (const contentTypeUid of contentTypesUids) {
785
+ const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
786
+ uid: contentTypeUid
787
+ });
788
+ contentTypesData[contentTypeUid] = {
789
+ mainField: contentTypeConfig.settings.mainField,
790
+ displayName: strapi2.getModel(contentTypeUid).info.displayName
791
+ };
792
+ }
793
+ return contentTypesData;
794
+ },
795
+ getContentTypeModelsFromActions(actions) {
796
+ const contentTypeUids = actions.reduce((acc, action) => {
797
+ if (!acc.includes(action.contentType)) {
798
+ acc.push(action.contentType);
799
+ }
533
800
  return acc;
534
- },
535
- {}
536
- );
537
- return componentsMap;
538
- },
539
- async delete(releaseId) {
540
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
541
- populate: {
542
- actions: {
543
- fields: ["id"]
801
+ }, []);
802
+ const contentTypeModelsMap = contentTypeUids.reduce(
803
+ (acc, contentTypeUid) => {
804
+ acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
805
+ return acc;
806
+ },
807
+ {}
808
+ );
809
+ return contentTypeModelsMap;
810
+ },
811
+ async getAllComponents() {
812
+ const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
813
+ const components = await contentManagerComponentsService.findAllComponents();
814
+ const componentsMap = components.reduce(
815
+ (acc, component) => {
816
+ acc[component.uid] = component;
817
+ return acc;
818
+ },
819
+ {}
820
+ );
821
+ return componentsMap;
822
+ },
823
+ async delete(releaseId) {
824
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
825
+ populate: {
826
+ actions: {
827
+ fields: ["id"]
828
+ }
544
829
  }
830
+ });
831
+ if (!release2) {
832
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
545
833
  }
546
- });
547
- if (!release2) {
548
- throw new errors.NotFoundError(`No release found for id ${releaseId}`);
549
- }
550
- if (release2.releasedAt) {
551
- throw new errors.ValidationError("Release already published");
552
- }
553
- await strapi2.db.transaction(async () => {
554
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
555
- where: {
556
- id: {
557
- $in: release2.actions.map((action) => action.id)
834
+ if (release2.releasedAt) {
835
+ throw new errors.ValidationError("Release already published");
836
+ }
837
+ await strapi2.db.transaction(async () => {
838
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
839
+ where: {
840
+ id: {
841
+ $in: release2.actions.map((action) => action.id)
842
+ }
558
843
  }
559
- }
844
+ });
845
+ await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
560
846
  });
561
- await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
562
- });
563
- return release2;
564
- },
565
- async publish(releaseId) {
566
- const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
567
- RELEASE_MODEL_UID,
568
- releaseId,
569
- {
570
- populate: {
571
- actions: {
847
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
848
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
849
+ await schedulingService.cancel(release2.id);
850
+ }
851
+ strapi2.telemetry.send("didDeleteContentRelease");
852
+ return release2;
853
+ },
854
+ async publish(releaseId) {
855
+ try {
856
+ const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
857
+ RELEASE_MODEL_UID,
858
+ releaseId,
859
+ {
572
860
  populate: {
573
- entry: {
574
- fields: ["id"]
861
+ actions: {
862
+ populate: {
863
+ entry: {
864
+ fields: ["id"]
865
+ }
866
+ }
575
867
  }
576
868
  }
577
869
  }
870
+ );
871
+ if (!releaseWithPopulatedActionEntries) {
872
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
578
873
  }
579
- }
580
- );
581
- if (!releaseWithPopulatedActionEntries) {
582
- throw new errors.NotFoundError(`No release found for id ${releaseId}`);
583
- }
584
- if (releaseWithPopulatedActionEntries.releasedAt) {
585
- throw new errors.ValidationError("Release already published");
586
- }
587
- if (releaseWithPopulatedActionEntries.actions.length === 0) {
588
- throw new errors.ValidationError("No entries to publish");
589
- }
590
- const collectionTypeActions = {};
591
- const singleTypeActions = [];
592
- for (const action of releaseWithPopulatedActionEntries.actions) {
593
- const contentTypeUid = action.contentType;
594
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
595
- if (!collectionTypeActions[contentTypeUid]) {
596
- collectionTypeActions[contentTypeUid] = {
597
- entriestoPublishIds: [],
598
- entriesToUnpublishIds: []
599
- };
874
+ if (releaseWithPopulatedActionEntries.releasedAt) {
875
+ throw new errors.ValidationError("Release already published");
600
876
  }
601
- if (action.type === "publish") {
602
- collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
603
- } else {
604
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
877
+ if (releaseWithPopulatedActionEntries.actions.length === 0) {
878
+ throw new errors.ValidationError("No entries to publish");
605
879
  }
606
- } else {
607
- singleTypeActions.push({
608
- uid: contentTypeUid,
609
- action: action.type,
610
- id: action.entry.id
611
- });
612
- }
613
- }
614
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
615
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
616
- await strapi2.db.transaction(async () => {
617
- for (const { uid, action, id } of singleTypeActions) {
618
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
619
- const entry = await strapi2.entityService.findOne(uid, id, { populate });
620
- try {
621
- if (action === "publish") {
622
- await entityManagerService.publish(entry, uid);
880
+ const collectionTypeActions = {};
881
+ const singleTypeActions = [];
882
+ for (const action of releaseWithPopulatedActionEntries.actions) {
883
+ const contentTypeUid = action.contentType;
884
+ if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
885
+ if (!collectionTypeActions[contentTypeUid]) {
886
+ collectionTypeActions[contentTypeUid] = {
887
+ entriestoPublishIds: [],
888
+ entriesToUnpublishIds: []
889
+ };
890
+ }
891
+ if (action.type === "publish") {
892
+ collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
893
+ } else {
894
+ collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
895
+ }
623
896
  } else {
624
- await entityManagerService.unpublish(entry, uid);
625
- }
626
- } catch (error) {
627
- if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
628
- ;
629
- else {
630
- throw error;
897
+ singleTypeActions.push({
898
+ uid: contentTypeUid,
899
+ action: action.type,
900
+ id: action.entry.id
901
+ });
631
902
  }
632
903
  }
633
- }
634
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
635
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
636
- const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
637
- const entriesToPublish = await strapi2.entityService.findMany(
638
- contentTypeUid,
639
- {
640
- filters: {
641
- id: {
642
- $in: entriestoPublishIds
904
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
905
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
906
+ await strapi2.db.transaction(async () => {
907
+ for (const { uid, action, id } of singleTypeActions) {
908
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
909
+ const entry = await strapi2.entityService.findOne(uid, id, { populate });
910
+ try {
911
+ if (action === "publish") {
912
+ await entityManagerService.publish(entry, uid);
913
+ } else {
914
+ await entityManagerService.unpublish(entry, uid);
643
915
  }
644
- },
645
- populate
916
+ } catch (error) {
917
+ if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
918
+ } else {
919
+ throw error;
920
+ }
921
+ }
646
922
  }
647
- );
648
- const entriesToUnpublish = await strapi2.entityService.findMany(
649
- contentTypeUid,
650
- {
651
- filters: {
652
- id: {
653
- $in: entriesToUnpublishIds
923
+ for (const contentTypeUid of Object.keys(collectionTypeActions)) {
924
+ const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
925
+ const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
926
+ const entriesToPublish = await strapi2.entityService.findMany(
927
+ contentTypeUid,
928
+ {
929
+ filters: {
930
+ id: {
931
+ $in: entriestoPublishIds
932
+ }
933
+ },
934
+ populate
654
935
  }
655
- },
656
- populate
936
+ );
937
+ const entriesToUnpublish = await strapi2.entityService.findMany(
938
+ contentTypeUid,
939
+ {
940
+ filters: {
941
+ id: {
942
+ $in: entriesToUnpublishIds
943
+ }
944
+ },
945
+ populate
946
+ }
947
+ );
948
+ if (entriesToPublish.length > 0) {
949
+ await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
950
+ }
951
+ if (entriesToUnpublish.length > 0) {
952
+ await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
953
+ }
657
954
  }
658
- );
659
- if (entriesToPublish.length > 0) {
660
- await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
955
+ });
956
+ const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
957
+ data: {
958
+ /*
959
+ * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
960
+ */
961
+ // @ts-expect-error see above
962
+ releasedAt: /* @__PURE__ */ new Date()
963
+ },
964
+ populate: {
965
+ actions: {
966
+ // @ts-expect-error is not expecting count but it is working
967
+ count: true
968
+ }
969
+ }
970
+ });
971
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
972
+ dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
973
+ isPublished: true,
974
+ release: release2
975
+ });
661
976
  }
662
- if (entriesToUnpublish.length > 0) {
663
- await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
977
+ strapi2.telemetry.send("didPublishContentRelease");
978
+ return release2;
979
+ } catch (error) {
980
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
981
+ dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
982
+ isPublished: false,
983
+ error
984
+ });
664
985
  }
986
+ strapi2.db.query(RELEASE_MODEL_UID).update({
987
+ where: { id: releaseId },
988
+ data: {
989
+ status: "failed"
990
+ }
991
+ });
992
+ throw error;
665
993
  }
666
- });
667
- const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
668
- data: {
669
- /*
670
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
671
- */
672
- // @ts-expect-error see above
673
- releasedAt: /* @__PURE__ */ new Date()
994
+ },
995
+ async updateAction(actionId, releaseId, update) {
996
+ const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
997
+ where: {
998
+ id: actionId,
999
+ release: {
1000
+ id: releaseId,
1001
+ releasedAt: {
1002
+ $null: true
1003
+ }
1004
+ }
1005
+ },
1006
+ data: update
1007
+ });
1008
+ if (!updatedAction) {
1009
+ throw new errors.NotFoundError(
1010
+ `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1011
+ );
674
1012
  }
675
- });
676
- return release2;
677
- },
678
- async updateAction(actionId, releaseId, update) {
679
- const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
680
- where: {
681
- id: actionId,
682
- release: {
683
- id: releaseId,
684
- releasedAt: {
685
- $null: true
1013
+ return updatedAction;
1014
+ },
1015
+ async deleteAction(actionId, releaseId) {
1016
+ const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
1017
+ where: {
1018
+ id: actionId,
1019
+ release: {
1020
+ id: releaseId,
1021
+ releasedAt: {
1022
+ $null: true
1023
+ }
686
1024
  }
687
1025
  }
688
- },
689
- data: update
690
- });
691
- if (!updatedAction) {
692
- throw new errors.NotFoundError(
693
- `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
694
- );
695
- }
696
- return updatedAction;
697
- },
698
- async deleteAction(actionId, releaseId) {
699
- const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
700
- where: {
701
- id: actionId,
702
- release: {
703
- id: releaseId,
704
- releasedAt: {
705
- $null: true
1026
+ });
1027
+ if (!deletedAction) {
1028
+ throw new errors.NotFoundError(
1029
+ `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1030
+ );
1031
+ }
1032
+ this.updateReleaseStatus(releaseId);
1033
+ return deletedAction;
1034
+ },
1035
+ async updateReleaseStatus(releaseId) {
1036
+ const [totalActions, invalidActions] = await Promise.all([
1037
+ this.countActions({
1038
+ filters: {
1039
+ release: releaseId
706
1040
  }
1041
+ }),
1042
+ this.countActions({
1043
+ filters: {
1044
+ release: releaseId,
1045
+ isEntryValid: false
1046
+ }
1047
+ })
1048
+ ]);
1049
+ if (totalActions > 0) {
1050
+ if (invalidActions > 0) {
1051
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
1052
+ where: {
1053
+ id: releaseId
1054
+ },
1055
+ data: {
1056
+ status: "blocked"
1057
+ }
1058
+ });
707
1059
  }
1060
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
1061
+ where: {
1062
+ id: releaseId
1063
+ },
1064
+ data: {
1065
+ status: "ready"
1066
+ }
1067
+ });
708
1068
  }
709
- });
710
- if (!deletedAction) {
711
- throw new errors.NotFoundError(
712
- `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
713
- );
1069
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
1070
+ where: {
1071
+ id: releaseId
1072
+ },
1073
+ data: {
1074
+ status: "empty"
1075
+ }
1076
+ });
714
1077
  }
715
- return deletedAction;
716
- }
717
- });
1078
+ };
1079
+ };
718
1080
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
719
1081
  async validateUniqueEntry(releaseId, releaseActionArgs) {
720
1082
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
@@ -839,8 +1201,23 @@ const services = {
839
1201
  };
840
1202
  const RELEASE_SCHEMA = yup.object().shape({
841
1203
  name: yup.string().trim().required(),
842
- // scheduledAt is a date, but we always receive strings from the client
843
- scheduledAt: yup.string().nullable()
1204
+ scheduledAt: yup.string().nullable(),
1205
+ isScheduled: yup.boolean().optional(),
1206
+ time: yup.string().when("isScheduled", {
1207
+ is: true,
1208
+ then: yup.string().trim().required(),
1209
+ otherwise: yup.string().nullable()
1210
+ }),
1211
+ timezone: yup.string().when("isScheduled", {
1212
+ is: true,
1213
+ then: yup.string().required().nullable(),
1214
+ otherwise: yup.string().nullable()
1215
+ }),
1216
+ date: yup.string().when("isScheduled", {
1217
+ is: true,
1218
+ then: yup.string().required().nullable(),
1219
+ otherwise: yup.string().nullable()
1220
+ })
844
1221
  }).required().noUnknown();
845
1222
  const validateRelease = validateYupSchema(RELEASE_SCHEMA);
846
1223
  const releaseController = {
@@ -873,7 +1250,12 @@ const releaseController = {
873
1250
  }
874
1251
  };
875
1252
  });
876
- ctx.body = { data, meta: { pagination } };
1253
+ const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
1254
+ where: {
1255
+ releasedAt: null
1256
+ }
1257
+ });
1258
+ ctx.body = { data, meta: { pagination, pendingReleasesCount } };
877
1259
  }
878
1260
  },
879
1261
  async findOne(ctx) {
@@ -1244,6 +1626,9 @@ const getPlugin = () => {
1244
1626
  };
1245
1627
  }
1246
1628
  return {
1629
+ // Always return register, it handles its own feature check
1630
+ register,
1631
+ // Always return contentTypes to avoid losing data when the feature is disabled
1247
1632
  contentTypes
1248
1633
  };
1249
1634
  };