@strapi/content-releases 0.0.0-next.c5f067b5650921187770124e9b6c8186e805e242 → 0.0.0-next.d470b4f75cf00f24f440b80300f1c833c322b871

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-5PsAyVt2.js → App-1hHIqUoZ.js} +127 -112
  2. package/dist/_chunks/App-1hHIqUoZ.js.map +1 -0
  3. package/dist/_chunks/{App-3ycH2d3s.mjs → App-U6GbyLIE.mjs} +129 -114
  4. package/dist/_chunks/App-U6GbyLIE.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-SOqjCdyh.mjs → en-GqXgfmzl.mjs} +6 -3
  10. package/dist/_chunks/en-GqXgfmzl.mjs.map +1 -0
  11. package/dist/_chunks/{en-2DuPv5k0.js → en-bDhIlw-B.js} +6 -3
  12. package/dist/_chunks/en-bDhIlw-B.js.map +1 -0
  13. package/dist/_chunks/{index-4gUWuCQV.mjs → index-gkExFBa0.mjs} +57 -16
  14. package/dist/_chunks/index-gkExFBa0.mjs.map +1 -0
  15. package/dist/_chunks/{index-D57Rztnc.js → index-l-FvkQlQ.js} +57 -16
  16. package/dist/_chunks/index-l-FvkQlQ.js.map +1 -0
  17. package/dist/admin/index.js +1 -1
  18. package/dist/admin/index.mjs +1 -1
  19. package/dist/server/index.js +139 -21
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +139 -21
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +10 -9
  24. package/dist/_chunks/App-3ycH2d3s.mjs.map +0 -1
  25. package/dist/_chunks/App-5PsAyVt2.js.map +0 -1
  26. package/dist/_chunks/en-2DuPv5k0.js.map +0 -1
  27. package/dist/_chunks/en-SOqjCdyh.mjs.map +0 -1
  28. package/dist/_chunks/index-4gUWuCQV.mjs.map +0 -1
  29. package/dist/_chunks/index-D57Rztnc.js.map +0 -1
@@ -2,6 +2,7 @@ import { contentTypes as contentTypes$1, mapAsync, setCreatorFields, errors, val
2
2
  import { difference, keys } from "lodash";
3
3
  import _ from "lodash/fp";
4
4
  import EE from "@strapi/strapi/dist/utils/ee";
5
+ import { scheduleJob } from "node-schedule";
5
6
  import * as yup from "yup";
6
7
  const RELEASE_MODEL_UID = "plugin::content-releases.release";
7
8
  const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
@@ -83,6 +84,9 @@ const register = async ({ strapi: strapi2 }) => {
83
84
  strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
84
85
  }
85
86
  };
87
+ const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
88
+ return strapi2.plugin("content-releases").service(name);
89
+ };
86
90
  const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
87
91
  const bootstrap = async ({ strapi: strapi2 }) => {
88
92
  if (features$1.isEnabled("cms-content-releases")) {
@@ -130,6 +134,24 @@ const bootstrap = async ({ strapi: strapi2 }) => {
130
134
  }
131
135
  }
132
136
  });
137
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
138
+ getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
139
+ strapi2.log.error(
140
+ "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
141
+ );
142
+ throw err;
143
+ });
144
+ }
145
+ }
146
+ };
147
+ const destroy = async ({ strapi: strapi2 }) => {
148
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
149
+ const scheduledJobs = getService("scheduling", {
150
+ strapi: strapi2
151
+ }).getAll();
152
+ for (const [, job] of scheduledJobs) {
153
+ job.cancel();
154
+ }
133
155
  }
134
156
  };
135
157
  const schema$1 = {
@@ -158,6 +180,12 @@ const schema$1 = {
158
180
  releasedAt: {
159
181
  type: "datetime"
160
182
  },
183
+ scheduledAt: {
184
+ type: "datetime"
185
+ },
186
+ timezone: {
187
+ type: "string"
188
+ },
161
189
  actions: {
162
190
  type: "relation",
163
191
  relation: "oneToMany",
@@ -220,9 +248,6 @@ const contentTypes = {
220
248
  release: release$1,
221
249
  "release-action": releaseAction$1
222
250
  };
223
- const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
224
- return strapi2.plugin("content-releases").service(name);
225
- };
226
251
  const getGroupName = (queryValue) => {
227
252
  switch (queryValue) {
228
253
  case "contentType":
@@ -238,17 +263,24 @@ const getGroupName = (queryValue) => {
238
263
  const createReleaseService = ({ strapi: strapi2 }) => ({
239
264
  async create(releaseData, { user }) {
240
265
  const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
241
- const { validatePendingReleasesLimit, validateUniqueNameForPendingRelease } = getService(
242
- "release-validation",
243
- { strapi: strapi2 }
244
- );
266
+ const {
267
+ validatePendingReleasesLimit,
268
+ validateUniqueNameForPendingRelease,
269
+ validateScheduledAtIsLaterThanNow
270
+ } = getService("release-validation", { strapi: strapi2 });
245
271
  await Promise.all([
246
272
  validatePendingReleasesLimit(),
247
- validateUniqueNameForPendingRelease(releaseWithCreatorFields.name)
273
+ validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
274
+ validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
248
275
  ]);
249
- return strapi2.entityService.create(RELEASE_MODEL_UID, {
276
+ const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
250
277
  data: releaseWithCreatorFields
251
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;
252
284
  },
253
285
  async findOne(id, query = {}) {
254
286
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
@@ -343,6 +375,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
343
375
  },
344
376
  async update(id, releaseData, { user }) {
345
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
+ ]);
346
386
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
347
387
  if (!release2) {
348
388
  throw new errors.NotFoundError(`No release found for id ${id}`);
@@ -358,6 +398,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
358
398
  // @ts-expect-error see above
359
399
  data: releaseWithCreatorFields
360
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);
407
+ }
408
+ }
361
409
  return updatedRelease;
362
410
  },
363
411
  async createAction(releaseId, action) {
@@ -515,6 +563,10 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
515
563
  });
516
564
  await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
517
565
  });
566
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
567
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
568
+ await schedulingService.cancel(release2.id);
569
+ }
518
570
  return release2;
519
571
  },
520
572
  async publish(releaseId) {
@@ -714,27 +766,93 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
714
766
  throw new errors.ValidationError("You have reached the maximum number of pending releases");
715
767
  }
716
768
  },
717
- async validateUniqueNameForPendingRelease(name) {
769
+ async validateUniqueNameForPendingRelease(name, id) {
718
770
  const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
719
771
  filters: {
720
772
  releasedAt: {
721
773
  $null: true
722
774
  },
723
- name
775
+ name,
776
+ ...id && { id: { $ne: id } }
724
777
  }
725
778
  });
726
779
  const isNameUnique = pendingReleases.length === 0;
727
780
  if (!isNameUnique) {
728
781
  throw new errors.ValidationError(`Release with name ${name} already exists`);
729
782
  }
783
+ },
784
+ async validateScheduledAtIsLaterThanNow(scheduledAt) {
785
+ if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
786
+ throw new errors.ValidationError("Scheduled at must be later than now");
787
+ }
730
788
  }
731
789
  });
790
+ const createSchedulingService = ({ strapi: strapi2 }) => {
791
+ const scheduledJobs = /* @__PURE__ */ new Map();
792
+ return {
793
+ async set(releaseId, scheduleDate) {
794
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
795
+ if (!release2) {
796
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
797
+ }
798
+ const job = scheduleJob(scheduleDate, async () => {
799
+ try {
800
+ await getService("release").publish(releaseId);
801
+ } catch (error) {
802
+ }
803
+ this.cancel(releaseId);
804
+ });
805
+ if (scheduledJobs.has(releaseId)) {
806
+ this.cancel(releaseId);
807
+ }
808
+ scheduledJobs.set(releaseId, job);
809
+ return scheduledJobs;
810
+ },
811
+ cancel(releaseId) {
812
+ if (scheduledJobs.has(releaseId)) {
813
+ scheduledJobs.get(releaseId).cancel();
814
+ scheduledJobs.delete(releaseId);
815
+ }
816
+ return scheduledJobs;
817
+ },
818
+ getAll() {
819
+ return scheduledJobs;
820
+ },
821
+ /**
822
+ * On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
823
+ * This is useful in case the server was restarted and the scheduled jobs were lost
824
+ * This also could be used to sync different Strapi instances in case of a cluster
825
+ */
826
+ async syncFromDatabase() {
827
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
828
+ where: {
829
+ scheduledAt: {
830
+ $gte: /* @__PURE__ */ new Date()
831
+ },
832
+ releasedAt: null
833
+ }
834
+ });
835
+ for (const release2 of releases) {
836
+ this.set(release2.id, release2.scheduledAt);
837
+ }
838
+ return scheduledJobs;
839
+ }
840
+ };
841
+ };
732
842
  const services = {
733
843
  release: createReleaseService,
734
- "release-validation": createReleaseValidationService
844
+ "release-validation": createReleaseValidationService,
845
+ ...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
735
846
  };
736
847
  const RELEASE_SCHEMA = yup.object().shape({
737
- name: yup.string().trim().required()
848
+ name: yup.string().trim().required(),
849
+ // scheduledAt is a date, but we always receive strings from the client
850
+ scheduledAt: yup.string().nullable(),
851
+ timezone: yup.string().when("scheduledAt", {
852
+ is: (scheduledAt) => !!scheduledAt,
853
+ then: yup.string().required(),
854
+ otherwise: yup.string().nullable()
855
+ })
738
856
  }).required().noUnknown();
739
857
  const validateRelease = validateYupSchema(RELEASE_SCHEMA);
740
858
  const releaseController = {
@@ -774,19 +892,18 @@ const releaseController = {
774
892
  const id = ctx.params.id;
775
893
  const releaseService = getService("release", { strapi });
776
894
  const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
777
- const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
778
- ability: ctx.state.userAbility,
779
- model: RELEASE_MODEL_UID
780
- });
781
- const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
895
+ if (!release2) {
896
+ throw new errors.NotFoundError(`Release not found for id: ${id}`);
897
+ }
782
898
  const count = await releaseService.countActions({
783
899
  filters: {
784
900
  release: id
785
901
  }
786
902
  });
787
- if (!release2) {
788
- throw new errors.NotFoundError(`Release not found for id: ${id}`);
789
- }
903
+ const sanitizedRelease = {
904
+ ...release2,
905
+ createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
906
+ };
790
907
  const data = {
791
908
  ...sanitizedRelease,
792
909
  actions: {
@@ -1131,6 +1248,7 @@ const getPlugin = () => {
1131
1248
  return {
1132
1249
  register,
1133
1250
  bootstrap,
1251
+ destroy,
1134
1252
  contentTypes,
1135
1253
  services,
1136
1254
  controllers,