@strapi/content-releases 4.20.2 → 4.20.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { 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
+ }
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
+ }
131
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
  };
@@ -186,6 +393,11 @@ const schema$1 = {
186
393
  timezone: {
187
394
  type: "string"
188
395
  },
396
+ status: {
397
+ type: "enumeration",
398
+ enum: ["ready", "blocked", "failed", "done", "empty"],
399
+ required: true
400
+ },
189
401
  actions: {
190
402
  type: "relation",
191
403
  relation: "oneToMany",
@@ -238,6 +450,9 @@ const schema = {
238
450
  relation: "manyToOne",
239
451
  target: RELEASE_MODEL_UID,
240
452
  inversedBy: "actions"
453
+ },
454
+ isEntryValid: {
455
+ type: "boolean"
241
456
  }
242
457
  }
243
458
  };
@@ -260,468 +475,563 @@ const getGroupName = (queryValue) => {
260
475
  return "contentType.displayName";
261
476
  }
262
477
  };
263
- const createReleaseService = ({ strapi: strapi2 }) => ({
264
- async create(releaseData, { user }) {
265
- const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
266
- const {
267
- validatePendingReleasesLimit,
268
- validateUniqueNameForPendingRelease,
269
- validateScheduledAtIsLaterThanNow
270
- } = getService("release-validation", { strapi: strapi2 });
271
- await Promise.all([
272
- validatePendingReleasesLimit(),
273
- validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
274
- validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
275
- ]);
276
- const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
277
- data: releaseWithCreatorFields
278
- });
279
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
280
- const schedulingService = getService("scheduling", { strapi: strapi2 });
281
- await schedulingService.set(release2.id, release2.scheduledAt);
282
- }
283
- return release2;
284
- },
285
- async findOne(id, query = {}) {
286
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
287
- ...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
288
484
  });
289
- return release2;
290
- },
291
- findPage(query) {
292
- return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
293
- ...query,
294
- populate: {
295
- actions: {
296
- // @ts-expect-error Ignore missing properties
297
- 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"
298
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);
299
508
  }
300
- });
301
- },
302
- async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
303
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
304
- where: {
305
- actions: {
306
- target_type: contentTypeUid,
307
- 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
+ }
308
539
  },
309
- releasedAt: {
310
- $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
+ }
311
548
  }
312
- },
313
- populate: {
314
- // Filter the action to get only the content type entry
315
- actions: {
316
- 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: {
317
569
  target_type: contentTypeUid,
318
570
  target_id: entryId
319
571
  }
320
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}`);
321
618
  }
322
- });
323
- return releases.map((release2) => {
324
- if (release2.actions?.length) {
325
- const [actionForEntry] = release2.actions;
326
- delete release2.actions;
327
- return {
328
- ...release2,
329
- action: actionForEntry
330
- };
619
+ if (release2.releasedAt) {
620
+ throw new errors.ValidationError("Release already published");
331
621
  }
332
- return release2;
333
- });
334
- },
335
- async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
336
- const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
337
- where: {
338
- releasedAt: {
339
- $null: true
340
- },
341
- actions: {
342
- target_type: contentTypeUid,
343
- 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);
344
636
  }
345
637
  }
346
- });
347
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
348
- where: {
349
- $or: [
350
- {
351
- id: {
352
- $notIn: releasesRelated.map((release2) => release2.id)
353
- }
354
- },
355
- {
356
- actions: null
357
- }
358
- ],
359
- releasedAt: {
360
- $null: true
361
- }
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}`);
362
653
  }
363
- });
364
- return releases.map((release2) => {
365
- if (release2.actions?.length) {
366
- const [actionForEntry] = release2.actions;
367
- delete release2.actions;
368
- return {
369
- ...release2,
370
- action: actionForEntry
371
- };
654
+ if (release2.releasedAt) {
655
+ throw new errors.ValidationError("Release already published");
372
656
  }
373
- return release2;
374
- });
375
- },
376
- async update(id, releaseData, { user }) {
377
- const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(releaseData);
378
- const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
379
- "release-validation",
380
- { strapi: strapi2 }
381
- );
382
- await Promise.all([
383
- validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
384
- validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
385
- ]);
386
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
387
- if (!release2) {
388
- throw new errors.NotFoundError(`No release found for id ${id}`);
389
- }
390
- if (release2.releasedAt) {
391
- throw new errors.ValidationError("Release already published");
392
- }
393
- const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
394
- /*
395
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
396
- * is not compatible with the type we are passing here: UpdateRelease.Request['body']
397
- */
398
- // @ts-expect-error see above
399
- data: releaseWithCreatorFields
400
- });
401
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
402
- const schedulingService = getService("scheduling", { strapi: strapi2 });
403
- if (releaseData.scheduledAt) {
404
- await schedulingService.set(id, releaseData.scheduledAt);
405
- } else if (release2.scheduledAt) {
406
- 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}`);
407
684
  }
408
- }
409
- return updatedRelease;
410
- },
411
- async createAction(releaseId, action) {
412
- const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
413
- strapi: strapi2
414
- });
415
- await Promise.all([
416
- validateEntryContentType(action.entry.contentType),
417
- validateUniqueEntry(releaseId, action)
418
- ]);
419
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
420
- if (!release2) {
421
- throw new errors.NotFoundError(`No release found for id ${releaseId}`);
422
- }
423
- if (release2.releasedAt) {
424
- throw new errors.ValidationError("Release already published");
425
- }
426
- const { entry, type } = action;
427
- return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
428
- data: {
429
- type,
430
- contentType: entry.contentType,
431
- locale: entry.locale,
432
- entry: {
433
- id: entry.id,
434
- __type: entry.contentType,
435
- __pivot: { field: "entry" }
685
+ return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
686
+ ...query,
687
+ populate: {
688
+ entry: {
689
+ populate: "*"
690
+ }
436
691
  },
437
- release: releaseId
438
- },
439
- populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
440
- });
441
- },
442
- async findActions(releaseId, query) {
443
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
444
- fields: ["id"]
445
- });
446
- if (!release2) {
447
- throw new errors.NotFoundError(`No release found for id ${releaseId}`);
448
- }
449
- return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
450
- ...query,
451
- populate: {
452
- entry: {
453
- populate: "*"
692
+ filters: {
693
+ release: releaseId
454
694
  }
455
- },
456
- filters: {
457
- release: releaseId
458
- }
459
- });
460
- },
461
- async countActions(query) {
462
- return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
463
- },
464
- async groupActions(actions, groupBy) {
465
- const contentTypeUids = actions.reduce((acc, action) => {
466
- if (!acc.includes(action.contentType)) {
467
- acc.push(action.contentType);
468
- }
469
- return acc;
470
- }, []);
471
- const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
472
- contentTypeUids
473
- );
474
- const allLocalesDictionary = await this.getLocalesDataForActions();
475
- const formattedData = actions.map((action) => {
476
- const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
477
- return {
478
- ...action,
479
- locale: action.locale ? allLocalesDictionary[action.locale] : null,
480
- contentType: {
481
- displayName,
482
- mainFieldValue: action.entry[mainField],
483
- 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);
484
704
  }
485
- };
486
- });
487
- const groupName = getGroupName(groupBy);
488
- return _.groupBy(groupName)(formattedData);
489
- },
490
- async getLocalesDataForActions() {
491
- if (!strapi2.plugin("i18n")) {
492
- return {};
493
- }
494
- const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
495
- return allLocales.reduce((acc, locale) => {
496
- acc[locale.code] = { name: locale.name, code: locale.code };
497
- return acc;
498
- }, {});
499
- },
500
- async getContentTypesDataForActions(contentTypesUids) {
501
- const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
502
- const contentTypesData = {};
503
- for (const contentTypeUid of contentTypesUids) {
504
- const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
505
- 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
+ };
506
722
  });
507
- contentTypesData[contentTypeUid] = {
508
- mainField: contentTypeConfig.settings.mainField,
509
- displayName: strapi2.getModel(contentTypeUid).info.displayName
510
- };
511
- }
512
- return contentTypesData;
513
- },
514
- getContentTypeModelsFromActions(actions) {
515
- const contentTypeUids = actions.reduce((acc, action) => {
516
- if (!acc.includes(action.contentType)) {
517
- 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 {};
518
729
  }
519
- return acc;
520
- }, []);
521
- const contentTypeModelsMap = contentTypeUids.reduce(
522
- (acc, contentTypeUid) => {
523
- 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 };
524
733
  return acc;
525
- },
526
- {}
527
- );
528
- return contentTypeModelsMap;
529
- },
530
- async getAllComponents() {
531
- const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
532
- const components = await contentManagerComponentsService.findAllComponents();
533
- const componentsMap = components.reduce(
534
- (acc, component) => {
535
- 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
+ }
536
755
  return acc;
537
- },
538
- {}
539
- );
540
- return componentsMap;
541
- },
542
- async delete(releaseId) {
543
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
544
- populate: {
545
- actions: {
546
- 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
+ }
547
784
  }
785
+ });
786
+ if (!release2) {
787
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
548
788
  }
549
- });
550
- if (!release2) {
551
- throw new errors.NotFoundError(`No release found for id ${releaseId}`);
552
- }
553
- if (release2.releasedAt) {
554
- throw new errors.ValidationError("Release already published");
555
- }
556
- await strapi2.db.transaction(async () => {
557
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
558
- where: {
559
- id: {
560
- $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
+ }
561
798
  }
562
- }
799
+ });
800
+ await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
563
801
  });
564
- await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
565
- });
566
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
567
- const schedulingService = getService("scheduling", { strapi: strapi2 });
568
- await schedulingService.cancel(release2.id);
569
- }
570
- return release2;
571
- },
572
- async publish(releaseId) {
573
- const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
574
- RELEASE_MODEL_UID,
575
- releaseId,
576
- {
577
- populate: {
578
- 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
+ {
579
815
  populate: {
580
- entry: {
581
- fields: ["id"]
816
+ actions: {
817
+ populate: {
818
+ entry: {
819
+ fields: ["id"]
820
+ }
821
+ }
582
822
  }
583
823
  }
584
824
  }
825
+ );
826
+ if (!releaseWithPopulatedActionEntries) {
827
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
585
828
  }
586
- }
587
- );
588
- if (!releaseWithPopulatedActionEntries) {
589
- throw new errors.NotFoundError(`No release found for id ${releaseId}`);
590
- }
591
- if (releaseWithPopulatedActionEntries.releasedAt) {
592
- throw new errors.ValidationError("Release already published");
593
- }
594
- if (releaseWithPopulatedActionEntries.actions.length === 0) {
595
- throw new errors.ValidationError("No entries to publish");
596
- }
597
- const collectionTypeActions = {};
598
- const singleTypeActions = [];
599
- for (const action of releaseWithPopulatedActionEntries.actions) {
600
- const contentTypeUid = action.contentType;
601
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
602
- if (!collectionTypeActions[contentTypeUid]) {
603
- collectionTypeActions[contentTypeUid] = {
604
- entriestoPublishIds: [],
605
- entriesToUnpublishIds: []
606
- };
829
+ if (releaseWithPopulatedActionEntries.releasedAt) {
830
+ throw new errors.ValidationError("Release already published");
607
831
  }
608
- if (action.type === "publish") {
609
- collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
610
- } else {
611
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
832
+ if (releaseWithPopulatedActionEntries.actions.length === 0) {
833
+ throw new errors.ValidationError("No entries to publish");
612
834
  }
613
- } else {
614
- singleTypeActions.push({
615
- uid: contentTypeUid,
616
- action: action.type,
617
- id: action.entry.id
618
- });
619
- }
620
- }
621
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
622
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
623
- await strapi2.db.transaction(async () => {
624
- for (const { uid, action, id } of singleTypeActions) {
625
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
626
- const entry = await strapi2.entityService.findOne(uid, id, { populate });
627
- try {
628
- if (action === "publish") {
629
- 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
+ }
630
851
  } else {
631
- await entityManagerService.unpublish(entry, uid);
632
- }
633
- } catch (error) {
634
- if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
635
- ;
636
- else {
637
- throw error;
852
+ singleTypeActions.push({
853
+ uid: contentTypeUid,
854
+ action: action.type,
855
+ id: action.entry.id
856
+ });
638
857
  }
639
858
  }
640
- }
641
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
642
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
643
- const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
644
- const entriesToPublish = await strapi2.entityService.findMany(
645
- contentTypeUid,
646
- {
647
- filters: {
648
- id: {
649
- $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);
650
870
  }
651
- },
652
- 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
+ }
653
877
  }
654
- );
655
- const entriesToUnpublish = await strapi2.entityService.findMany(
656
- contentTypeUid,
657
- {
658
- filters: {
659
- id: {
660
- $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
661
890
  }
662
- },
663
- 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
+ }
664
909
  }
665
- );
666
- if (entriesToPublish.length > 0) {
667
- 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
+ });
668
931
  }
669
- if (entriesToUnpublish.length > 0) {
670
- 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
+ });
671
940
  }
941
+ strapi2.db.query(RELEASE_MODEL_UID).update({
942
+ where: { id: releaseId },
943
+ data: {
944
+ status: "failed"
945
+ }
946
+ });
947
+ throw error;
672
948
  }
673
- });
674
- const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
675
- data: {
676
- /*
677
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
678
- */
679
- // @ts-expect-error see above
680
- 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
+ );
681
967
  }
682
- });
683
- return release2;
684
- },
685
- async updateAction(actionId, releaseId, update) {
686
- const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
687
- where: {
688
- id: actionId,
689
- release: {
690
- id: releaseId,
691
- releasedAt: {
692
- $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
+ }
693
979
  }
694
980
  }
695
- },
696
- data: update
697
- });
698
- if (!updatedAction) {
699
- throw new errors.NotFoundError(
700
- `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
701
- );
702
- }
703
- return updatedAction;
704
- },
705
- async deleteAction(actionId, releaseId) {
706
- const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
707
- where: {
708
- id: actionId,
709
- release: {
710
- id: releaseId,
711
- releasedAt: {
712
- $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
713
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
+ });
714
1014
  }
1015
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
1016
+ where: {
1017
+ id: releaseId
1018
+ },
1019
+ data: {
1020
+ status: "ready"
1021
+ }
1022
+ });
715
1023
  }
716
- });
717
- if (!deletedAction) {
718
- throw new errors.NotFoundError(
719
- `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
720
- );
1024
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
1025
+ where: {
1026
+ id: releaseId
1027
+ },
1028
+ data: {
1029
+ status: "empty"
1030
+ }
1031
+ });
721
1032
  }
722
- return deletedAction;
723
- }
724
- });
1033
+ };
1034
+ };
725
1035
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
726
1036
  async validateUniqueEntry(releaseId, releaseActionArgs) {
727
1037
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
@@ -846,11 +1156,21 @@ const services = {
846
1156
  };
847
1157
  const RELEASE_SCHEMA = yup.object().shape({
848
1158
  name: yup.string().trim().required(),
849
- // scheduledAt is a date, but we always receive strings from the client
850
1159
  scheduledAt: yup.string().nullable(),
851
- timezone: yup.string().when("scheduledAt", {
852
- is: (scheduledAt) => !!scheduledAt,
853
- then: yup.string().required(),
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(),
854
1174
  otherwise: yup.string().nullable()
855
1175
  })
856
1176
  }).required().noUnknown();
@@ -885,7 +1205,12 @@ const releaseController = {
885
1205
  }
886
1206
  };
887
1207
  });
888
- 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 } };
889
1214
  }
890
1215
  },
891
1216
  async findOne(ctx) {