@strapi/content-releases 0.0.0-next.aa7c7ec6724534e157d8a23fe85ee8318dabbf37 → 0.0.0-next.b6d552f6e63dec5627cb8611ab2adcb8244359be

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-pspKUC-W.js → App-5G7GEzBM.js} +292 -69
  2. package/dist/_chunks/App-5G7GEzBM.js.map +1 -0
  3. package/dist/_chunks/{App-8FCxPK8-.mjs → App-WMxox0mk.mjs} +293 -71
  4. package/dist/_chunks/App-WMxox0mk.mjs.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-BZ8RPGiV.mjs} +91 -14
  14. package/dist/_chunks/index-BZ8RPGiV.mjs.map +1 -0
  15. package/dist/_chunks/{index-nGaPcY9m.js → index-pQ3hnZJy.js} +87 -10
  16. package/dist/_chunks/index-pQ3hnZJy.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 +769 -431
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +768 -431
  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,151 @@ 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) {
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
+ }
79
210
  const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
80
211
  const register = async ({ strapi: strapi2 }) => {
81
212
  if (features$2.isEnabled("cms-content-releases")) {
82
213
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
83
214
  strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
84
- strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
215
+ strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
85
216
  }
86
217
  };
87
- const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
88
- return strapi2.plugin("content-releases").service(name);
89
- };
90
218
  const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
91
219
  const bootstrap = async ({ strapi: strapi2 }) => {
92
220
  if (features$1.isEnabled("cms-content-releases")) {
221
+ const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
222
+ (uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
223
+ );
93
224
  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
225
+ models: contentTypesWithDraftAndPublish,
226
+ async afterDelete(event) {
227
+ try {
228
+ const { model, result } = event;
229
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
230
+ const { id } = result;
231
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
232
+ where: {
233
+ actions: {
234
+ target_type: model.uid,
235
+ target_id: id
236
+ }
237
+ }
238
+ });
239
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
240
+ where: {
241
+ target_type: model.uid,
242
+ target_id: id
243
+ }
244
+ });
245
+ for (const release2 of releases) {
246
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
102
247
  }
103
- });
248
+ }
249
+ } catch (error) {
250
+ strapi2.log.error("Error while deleting release actions after entry delete", { error });
104
251
  }
105
252
  },
106
253
  /**
@@ -120,18 +267,75 @@ const bootstrap = async ({ strapi: strapi2 }) => {
120
267
  * We make this only after deleteMany is succesfully executed to avoid errors
121
268
  */
122
269
  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)
270
+ try {
271
+ const { model, state } = event;
272
+ const entriesToDelete = state.entriesToDelete;
273
+ if (entriesToDelete) {
274
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
275
+ where: {
276
+ actions: {
277
+ target_type: model.uid,
278
+ target_id: {
279
+ $in: entriesToDelete.map(
280
+ (entry) => entry.id
281
+ )
282
+ }
283
+ }
131
284
  }
285
+ });
286
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
287
+ where: {
288
+ target_type: model.uid,
289
+ target_id: {
290
+ $in: entriesToDelete.map((entry) => entry.id)
291
+ }
292
+ }
293
+ });
294
+ for (const release2 of releases) {
295
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
132
296
  }
297
+ }
298
+ } catch (error) {
299
+ strapi2.log.error("Error while deleting release actions after entry deleteMany", {
300
+ error
133
301
  });
134
302
  }
303
+ },
304
+ async afterUpdate(event) {
305
+ try {
306
+ const { model, result } = event;
307
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
308
+ const isEntryValid = await getEntryValidStatus(
309
+ model.uid,
310
+ result,
311
+ {
312
+ strapi: strapi2
313
+ }
314
+ );
315
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
316
+ where: {
317
+ target_type: model.uid,
318
+ target_id: result.id
319
+ },
320
+ data: {
321
+ isEntryValid
322
+ }
323
+ });
324
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
325
+ where: {
326
+ actions: {
327
+ target_type: model.uid,
328
+ target_id: result.id
329
+ }
330
+ }
331
+ });
332
+ for (const release2 of releases) {
333
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
334
+ }
335
+ }
336
+ } catch (error) {
337
+ strapi2.log.error("Error while updating release actions after entry update", { error });
338
+ }
135
339
  }
136
340
  });
137
341
  if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
@@ -141,6 +345,9 @@ const bootstrap = async ({ strapi: strapi2 }) => {
141
345
  );
142
346
  throw err;
143
347
  });
348
+ Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
349
+ strapi2.webhookStore.addAllowedEvent(key, value);
350
+ });
144
351
  }
145
352
  }
146
353
  };
@@ -183,6 +390,14 @@ const schema$1 = {
183
390
  scheduledAt: {
184
391
  type: "datetime"
185
392
  },
393
+ timezone: {
394
+ type: "string"
395
+ },
396
+ status: {
397
+ type: "enumeration",
398
+ enum: ["ready", "blocked", "failed", "done", "empty"],
399
+ required: true
400
+ },
186
401
  actions: {
187
402
  type: "relation",
188
403
  relation: "oneToMany",
@@ -235,6 +450,9 @@ const schema = {
235
450
  relation: "manyToOne",
236
451
  target: RELEASE_MODEL_UID,
237
452
  inversedBy: "actions"
453
+ },
454
+ isEntryValid: {
455
+ type: "boolean"
238
456
  }
239
457
  }
240
458
  };
@@ -257,464 +475,563 @@ const getGroupName = (queryValue) => {
257
475
  return "contentType.displayName";
258
476
  }
259
477
  };
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
275
- });
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
478
+ const createReleaseService = ({ strapi: strapi2 }) => {
479
+ const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
480
+ strapi2.eventHub.emit(event, {
481
+ isPublished,
482
+ error,
483
+ release: release2
285
484
  });
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
485
+ };
486
+ return {
487
+ async create(releaseData, { user }) {
488
+ const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
489
+ const {
490
+ validatePendingReleasesLimit,
491
+ validateUniqueNameForPendingRelease,
492
+ validateScheduledAtIsLaterThanNow
493
+ } = getService("release-validation", { strapi: strapi2 });
494
+ await Promise.all([
495
+ validatePendingReleasesLimit(),
496
+ validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
497
+ validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
498
+ ]);
499
+ const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
500
+ data: {
501
+ ...releaseWithCreatorFields,
502
+ status: "empty"
295
503
  }
504
+ });
505
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
506
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
507
+ await schedulingService.set(release2.id, release2.scheduledAt);
296
508
  }
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
509
+ strapi2.telemetry.send("didCreateContentRelease");
510
+ return release2;
511
+ },
512
+ async findOne(id, query = {}) {
513
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
514
+ ...query
515
+ });
516
+ return release2;
517
+ },
518
+ findPage(query) {
519
+ return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
520
+ ...query,
521
+ populate: {
522
+ actions: {
523
+ // @ts-expect-error Ignore missing properties
524
+ count: true
525
+ }
526
+ }
527
+ });
528
+ },
529
+ async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
530
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
531
+ where: {
532
+ actions: {
533
+ target_type: contentTypeUid,
534
+ target_id: entryId
535
+ },
536
+ releasedAt: {
537
+ $null: true
538
+ }
305
539
  },
306
- releasedAt: {
307
- $null: true
540
+ populate: {
541
+ // Filter the action to get only the content type entry
542
+ actions: {
543
+ where: {
544
+ target_type: contentTypeUid,
545
+ target_id: entryId
546
+ }
547
+ }
308
548
  }
309
- },
310
- populate: {
311
- // Filter the action to get only the content type entry
312
- actions: {
313
- where: {
549
+ });
550
+ return releases.map((release2) => {
551
+ if (release2.actions?.length) {
552
+ const [actionForEntry] = release2.actions;
553
+ delete release2.actions;
554
+ return {
555
+ ...release2,
556
+ action: actionForEntry
557
+ };
558
+ }
559
+ return release2;
560
+ });
561
+ },
562
+ async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
563
+ const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
564
+ where: {
565
+ releasedAt: {
566
+ $null: true
567
+ },
568
+ actions: {
314
569
  target_type: contentTypeUid,
315
570
  target_id: entryId
316
571
  }
317
572
  }
573
+ });
574
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
575
+ where: {
576
+ $or: [
577
+ {
578
+ id: {
579
+ $notIn: releasesRelated.map((release2) => release2.id)
580
+ }
581
+ },
582
+ {
583
+ actions: null
584
+ }
585
+ ],
586
+ releasedAt: {
587
+ $null: true
588
+ }
589
+ }
590
+ });
591
+ return releases.map((release2) => {
592
+ if (release2.actions?.length) {
593
+ const [actionForEntry] = release2.actions;
594
+ delete release2.actions;
595
+ return {
596
+ ...release2,
597
+ action: actionForEntry
598
+ };
599
+ }
600
+ return release2;
601
+ });
602
+ },
603
+ async update(id, releaseData, { user }) {
604
+ const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(
605
+ releaseData
606
+ );
607
+ const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
608
+ "release-validation",
609
+ { strapi: strapi2 }
610
+ );
611
+ await Promise.all([
612
+ validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
613
+ validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
614
+ ]);
615
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
616
+ if (!release2) {
617
+ throw new errors.NotFoundError(`No release found for id ${id}`);
318
618
  }
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
- };
619
+ if (release2.releasedAt) {
620
+ throw new errors.ValidationError("Release already published");
328
621
  }
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
622
+ const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
623
+ /*
624
+ * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
625
+ * is not compatible with the type we are passing here: UpdateRelease.Request['body']
626
+ */
627
+ // @ts-expect-error see above
628
+ data: releaseWithCreatorFields
629
+ });
630
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
631
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
632
+ if (releaseData.scheduledAt) {
633
+ await schedulingService.set(id, releaseData.scheduledAt);
634
+ } else if (release2.scheduledAt) {
635
+ schedulingService.cancel(id);
341
636
  }
342
637
  }
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
- }
638
+ this.updateReleaseStatus(id);
639
+ strapi2.telemetry.send("didUpdateContentRelease");
640
+ return updatedRelease;
641
+ },
642
+ async createAction(releaseId, action) {
643
+ const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
644
+ strapi: strapi2
645
+ });
646
+ await Promise.all([
647
+ validateEntryContentType(action.entry.contentType),
648
+ validateUniqueEntry(releaseId, action)
649
+ ]);
650
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
651
+ if (!release2) {
652
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
359
653
  }
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
- };
654
+ if (release2.releasedAt) {
655
+ throw new errors.ValidationError("Release already published");
369
656
  }
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);
657
+ const { entry, type } = action;
658
+ const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
659
+ const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
660
+ const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
661
+ data: {
662
+ type,
663
+ contentType: entry.contentType,
664
+ locale: entry.locale,
665
+ isEntryValid,
666
+ entry: {
667
+ id: entry.id,
668
+ __type: entry.contentType,
669
+ __pivot: { field: "entry" }
670
+ },
671
+ release: releaseId
672
+ },
673
+ populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
674
+ });
675
+ this.updateReleaseStatus(releaseId);
676
+ return releaseAction2;
677
+ },
678
+ async findActions(releaseId, query) {
679
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
680
+ fields: ["id"]
681
+ });
682
+ if (!release2) {
683
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
404
684
  }
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" }
685
+ return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
686
+ ...query,
687
+ populate: {
688
+ entry: {
689
+ populate: "*"
690
+ }
433
691
  },
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: "*"
692
+ filters: {
693
+ release: releaseId
451
694
  }
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
695
+ });
696
+ },
697
+ async countActions(query) {
698
+ return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
699
+ },
700
+ async groupActions(actions, groupBy) {
701
+ const contentTypeUids = actions.reduce((acc, action) => {
702
+ if (!acc.includes(action.contentType)) {
703
+ acc.push(action.contentType);
481
704
  }
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
705
+ return acc;
706
+ }, []);
707
+ const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
708
+ contentTypeUids
709
+ );
710
+ const allLocalesDictionary = await this.getLocalesDataForActions();
711
+ const formattedData = actions.map((action) => {
712
+ const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
713
+ return {
714
+ ...action,
715
+ locale: action.locale ? allLocalesDictionary[action.locale] : null,
716
+ contentType: {
717
+ displayName,
718
+ mainFieldValue: action.entry[mainField],
719
+ uid: action.contentType
720
+ }
721
+ };
503
722
  });
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);
723
+ const groupName = getGroupName(groupBy);
724
+ return _.groupBy(groupName)(formattedData);
725
+ },
726
+ async getLocalesDataForActions() {
727
+ if (!strapi2.plugin("i18n")) {
728
+ return {};
515
729
  }
516
- return acc;
517
- }, []);
518
- const contentTypeModelsMap = contentTypeUids.reduce(
519
- (acc, contentTypeUid) => {
520
- acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
730
+ const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
731
+ return allLocales.reduce((acc, locale) => {
732
+ acc[locale.code] = { name: locale.name, code: locale.code };
521
733
  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;
734
+ }, {});
735
+ },
736
+ async getContentTypesDataForActions(contentTypesUids) {
737
+ const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
738
+ const contentTypesData = {};
739
+ for (const contentTypeUid of contentTypesUids) {
740
+ const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
741
+ uid: contentTypeUid
742
+ });
743
+ contentTypesData[contentTypeUid] = {
744
+ mainField: contentTypeConfig.settings.mainField,
745
+ displayName: strapi2.getModel(contentTypeUid).info.displayName
746
+ };
747
+ }
748
+ return contentTypesData;
749
+ },
750
+ getContentTypeModelsFromActions(actions) {
751
+ const contentTypeUids = actions.reduce((acc, action) => {
752
+ if (!acc.includes(action.contentType)) {
753
+ acc.push(action.contentType);
754
+ }
533
755
  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"]
756
+ }, []);
757
+ const contentTypeModelsMap = contentTypeUids.reduce(
758
+ (acc, contentTypeUid) => {
759
+ acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
760
+ return acc;
761
+ },
762
+ {}
763
+ );
764
+ return contentTypeModelsMap;
765
+ },
766
+ async getAllComponents() {
767
+ const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
768
+ const components = await contentManagerComponentsService.findAllComponents();
769
+ const componentsMap = components.reduce(
770
+ (acc, component) => {
771
+ acc[component.uid] = component;
772
+ return acc;
773
+ },
774
+ {}
775
+ );
776
+ return componentsMap;
777
+ },
778
+ async delete(releaseId) {
779
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
780
+ populate: {
781
+ actions: {
782
+ fields: ["id"]
783
+ }
544
784
  }
785
+ });
786
+ if (!release2) {
787
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
545
788
  }
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)
789
+ if (release2.releasedAt) {
790
+ throw new errors.ValidationError("Release already published");
791
+ }
792
+ await strapi2.db.transaction(async () => {
793
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
794
+ where: {
795
+ id: {
796
+ $in: release2.actions.map((action) => action.id)
797
+ }
558
798
  }
559
- }
799
+ });
800
+ await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
560
801
  });
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: {
802
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
803
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
804
+ await schedulingService.cancel(release2.id);
805
+ }
806
+ strapi2.telemetry.send("didDeleteContentRelease");
807
+ return release2;
808
+ },
809
+ async publish(releaseId) {
810
+ try {
811
+ const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
812
+ RELEASE_MODEL_UID,
813
+ releaseId,
814
+ {
572
815
  populate: {
573
- entry: {
574
- fields: ["id"]
816
+ actions: {
817
+ populate: {
818
+ entry: {
819
+ fields: ["id"]
820
+ }
821
+ }
575
822
  }
576
823
  }
577
824
  }
825
+ );
826
+ if (!releaseWithPopulatedActionEntries) {
827
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
578
828
  }
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
- };
829
+ if (releaseWithPopulatedActionEntries.releasedAt) {
830
+ throw new errors.ValidationError("Release already published");
600
831
  }
601
- if (action.type === "publish") {
602
- collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
603
- } else {
604
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
832
+ if (releaseWithPopulatedActionEntries.actions.length === 0) {
833
+ throw new errors.ValidationError("No entries to publish");
605
834
  }
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);
835
+ const collectionTypeActions = {};
836
+ const singleTypeActions = [];
837
+ for (const action of releaseWithPopulatedActionEntries.actions) {
838
+ const contentTypeUid = action.contentType;
839
+ if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
840
+ if (!collectionTypeActions[contentTypeUid]) {
841
+ collectionTypeActions[contentTypeUid] = {
842
+ entriestoPublishIds: [],
843
+ entriesToUnpublishIds: []
844
+ };
845
+ }
846
+ if (action.type === "publish") {
847
+ collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
848
+ } else {
849
+ collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
850
+ }
623
851
  } 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;
852
+ singleTypeActions.push({
853
+ uid: contentTypeUid,
854
+ action: action.type,
855
+ id: action.entry.id
856
+ });
631
857
  }
632
858
  }
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
859
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
860
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
861
+ await strapi2.db.transaction(async () => {
862
+ for (const { uid, action, id } of singleTypeActions) {
863
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
864
+ const entry = await strapi2.entityService.findOne(uid, id, { populate });
865
+ try {
866
+ if (action === "publish") {
867
+ await entityManagerService.publish(entry, uid);
868
+ } else {
869
+ await entityManagerService.unpublish(entry, uid);
643
870
  }
644
- },
645
- populate
871
+ } catch (error) {
872
+ if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
873
+ } else {
874
+ throw error;
875
+ }
876
+ }
646
877
  }
647
- );
648
- const entriesToUnpublish = await strapi2.entityService.findMany(
649
- contentTypeUid,
650
- {
651
- filters: {
652
- id: {
653
- $in: entriesToUnpublishIds
878
+ for (const contentTypeUid of Object.keys(collectionTypeActions)) {
879
+ const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
880
+ const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
881
+ const entriesToPublish = await strapi2.entityService.findMany(
882
+ contentTypeUid,
883
+ {
884
+ filters: {
885
+ id: {
886
+ $in: entriestoPublishIds
887
+ }
888
+ },
889
+ populate
654
890
  }
655
- },
656
- populate
891
+ );
892
+ const entriesToUnpublish = await strapi2.entityService.findMany(
893
+ contentTypeUid,
894
+ {
895
+ filters: {
896
+ id: {
897
+ $in: entriesToUnpublishIds
898
+ }
899
+ },
900
+ populate
901
+ }
902
+ );
903
+ if (entriesToPublish.length > 0) {
904
+ await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
905
+ }
906
+ if (entriesToUnpublish.length > 0) {
907
+ await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
908
+ }
657
909
  }
658
- );
659
- if (entriesToPublish.length > 0) {
660
- await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
910
+ });
911
+ const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
912
+ data: {
913
+ /*
914
+ * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
915
+ */
916
+ // @ts-expect-error see above
917
+ releasedAt: /* @__PURE__ */ new Date()
918
+ },
919
+ populate: {
920
+ actions: {
921
+ // @ts-expect-error is not expecting count but it is working
922
+ count: true
923
+ }
924
+ }
925
+ });
926
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
927
+ dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
928
+ isPublished: true,
929
+ release: release2
930
+ });
661
931
  }
662
- if (entriesToUnpublish.length > 0) {
663
- await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
932
+ strapi2.telemetry.send("didPublishContentRelease");
933
+ return release2;
934
+ } catch (error) {
935
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
936
+ dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
937
+ isPublished: false,
938
+ error
939
+ });
664
940
  }
941
+ strapi2.db.query(RELEASE_MODEL_UID).update({
942
+ where: { id: releaseId },
943
+ data: {
944
+ status: "failed"
945
+ }
946
+ });
947
+ throw error;
665
948
  }
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()
949
+ },
950
+ async updateAction(actionId, releaseId, update) {
951
+ const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
952
+ where: {
953
+ id: actionId,
954
+ release: {
955
+ id: releaseId,
956
+ releasedAt: {
957
+ $null: true
958
+ }
959
+ }
960
+ },
961
+ data: update
962
+ });
963
+ if (!updatedAction) {
964
+ throw new errors.NotFoundError(
965
+ `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
966
+ );
674
967
  }
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
968
+ return updatedAction;
969
+ },
970
+ async deleteAction(actionId, releaseId) {
971
+ const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
972
+ where: {
973
+ id: actionId,
974
+ release: {
975
+ id: releaseId,
976
+ releasedAt: {
977
+ $null: true
978
+ }
686
979
  }
687
980
  }
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
981
+ });
982
+ if (!deletedAction) {
983
+ throw new errors.NotFoundError(
984
+ `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
985
+ );
986
+ }
987
+ this.updateReleaseStatus(releaseId);
988
+ return deletedAction;
989
+ },
990
+ async updateReleaseStatus(releaseId) {
991
+ const [totalActions, invalidActions] = await Promise.all([
992
+ this.countActions({
993
+ filters: {
994
+ release: releaseId
995
+ }
996
+ }),
997
+ this.countActions({
998
+ filters: {
999
+ release: releaseId,
1000
+ isEntryValid: false
706
1001
  }
1002
+ })
1003
+ ]);
1004
+ if (totalActions > 0) {
1005
+ if (invalidActions > 0) {
1006
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
1007
+ where: {
1008
+ id: releaseId
1009
+ },
1010
+ data: {
1011
+ status: "blocked"
1012
+ }
1013
+ });
707
1014
  }
1015
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
1016
+ where: {
1017
+ id: releaseId
1018
+ },
1019
+ data: {
1020
+ status: "ready"
1021
+ }
1022
+ });
708
1023
  }
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
- );
1024
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
1025
+ where: {
1026
+ id: releaseId
1027
+ },
1028
+ data: {
1029
+ status: "empty"
1030
+ }
1031
+ });
714
1032
  }
715
- return deletedAction;
716
- }
717
- });
1033
+ };
1034
+ };
718
1035
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
719
1036
  async validateUniqueEntry(releaseId, releaseActionArgs) {
720
1037
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
@@ -839,8 +1156,23 @@ const services = {
839
1156
  };
840
1157
  const RELEASE_SCHEMA = yup.object().shape({
841
1158
  name: yup.string().trim().required(),
842
- // scheduledAt is a date, but we always receive strings from the client
843
- scheduledAt: yup.string().nullable()
1159
+ scheduledAt: yup.string().nullable(),
1160
+ isScheduled: yup.boolean().optional(),
1161
+ time: yup.string().when("isScheduled", {
1162
+ is: true,
1163
+ then: yup.string().trim().required(),
1164
+ otherwise: yup.string().nullable()
1165
+ }),
1166
+ timezone: yup.string().when("isScheduled", {
1167
+ is: true,
1168
+ then: yup.string().required().nullable(),
1169
+ otherwise: yup.string().nullable()
1170
+ }),
1171
+ date: yup.string().when("isScheduled", {
1172
+ is: true,
1173
+ then: yup.string().required().nullable(),
1174
+ otherwise: yup.string().nullable()
1175
+ })
844
1176
  }).required().noUnknown();
845
1177
  const validateRelease = validateYupSchema(RELEASE_SCHEMA);
846
1178
  const releaseController = {
@@ -873,7 +1205,12 @@ const releaseController = {
873
1205
  }
874
1206
  };
875
1207
  });
876
- ctx.body = { data, meta: { pagination } };
1208
+ const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
1209
+ where: {
1210
+ releasedAt: null
1211
+ }
1212
+ });
1213
+ ctx.body = { data, meta: { pagination, pendingReleasesCount } };
877
1214
  }
878
1215
  },
879
1216
  async findOne(ctx) {