@strapi/content-releases 0.0.0-next.e1ede8c55a0e1e22ce20137bf238fc374bd5dd51 → 0.0.0-next.f8af92b375dc730ba47ed2117f25df893aae696c
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.
- package/dist/_chunks/{App-o5_WfqR-.js → App-OK4Xac-O.js} +572 -224
- package/dist/_chunks/App-OK4Xac-O.js.map +1 -0
- package/dist/_chunks/App-xAkiD42p.mjs +1292 -0
- package/dist/_chunks/App-xAkiD42p.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs +51 -0
- package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js +51 -0
- package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +1 -0
- package/dist/_chunks/{en-haKSQIo8.js → en-r0otWaln.js} +19 -4
- package/dist/_chunks/en-r0otWaln.js.map +1 -0
- package/dist/_chunks/{en-ngTk74JV.mjs → en-veqvqeEr.mjs} +19 -4
- package/dist/_chunks/en-veqvqeEr.mjs.map +1 -0
- package/dist/_chunks/{index-EdBmRHRU.js → index-JvA2_26n.js} +220 -54
- package/dist/_chunks/index-JvA2_26n.js.map +1 -0
- package/dist/_chunks/{index-XAQOX_IB.mjs → index-exoiSU3V.mjs} +231 -65
- package/dist/_chunks/index-exoiSU3V.mjs.map +1 -0
- package/dist/admin/index.js +2 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +3 -2
- package/dist/admin/index.mjs.map +1 -1
- package/dist/server/index.js +749 -302
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +749 -303
- package/dist/server/index.mjs.map +1 -1
- package/package.json +13 -9
- package/dist/_chunks/App-g2P5kbSm.mjs +0 -945
- package/dist/_chunks/App-g2P5kbSm.mjs.map +0 -1
- package/dist/_chunks/App-o5_WfqR-.js.map +0 -1
- package/dist/_chunks/en-haKSQIo8.js.map +0 -1
- package/dist/_chunks/en-ngTk74JV.mjs.map +0 -1
- package/dist/_chunks/index-EdBmRHRU.js.map +0 -1
- package/dist/_chunks/index-XAQOX_IB.mjs.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const utils = require("@strapi/utils");
|
|
3
|
+
const lodash = require("lodash");
|
|
3
4
|
const _ = require("lodash/fp");
|
|
5
|
+
const EE = require("@strapi/strapi/dist/utils/ee");
|
|
6
|
+
const nodeSchedule = require("node-schedule");
|
|
4
7
|
const yup = require("yup");
|
|
5
8
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
6
9
|
function _interopNamespace(e) {
|
|
@@ -22,6 +25,7 @@ function _interopNamespace(e) {
|
|
|
22
25
|
return Object.freeze(n);
|
|
23
26
|
}
|
|
24
27
|
const ___default = /* @__PURE__ */ _interopDefault(_);
|
|
28
|
+
const EE__default = /* @__PURE__ */ _interopDefault(EE);
|
|
25
29
|
const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
|
|
26
30
|
const RELEASE_MODEL_UID = "plugin::content-releases.release";
|
|
27
31
|
const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
|
|
@@ -69,10 +73,114 @@ const ACTIONS = [
|
|
|
69
73
|
pluginName: "content-releases"
|
|
70
74
|
}
|
|
71
75
|
];
|
|
72
|
-
const
|
|
76
|
+
const ALLOWED_WEBHOOK_EVENTS = {
|
|
77
|
+
RELEASES_PUBLISH: "releases.publish"
|
|
78
|
+
};
|
|
79
|
+
async function deleteActionsOnDisableDraftAndPublish({
|
|
80
|
+
oldContentTypes,
|
|
81
|
+
contentTypes: contentTypes2
|
|
82
|
+
}) {
|
|
83
|
+
if (!oldContentTypes) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
for (const uid in contentTypes2) {
|
|
87
|
+
if (!oldContentTypes[uid]) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const oldContentType = oldContentTypes[uid];
|
|
91
|
+
const contentType = contentTypes2[uid];
|
|
92
|
+
if (utils.contentTypes.hasDraftAndPublish(oldContentType) && !utils.contentTypes.hasDraftAndPublish(contentType)) {
|
|
93
|
+
await strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: uid }).execute();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
98
|
+
const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
|
|
99
|
+
if (deletedContentTypes.length) {
|
|
100
|
+
await utils.mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
|
|
101
|
+
return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
|
|
73
106
|
const register = async ({ strapi: strapi2 }) => {
|
|
74
|
-
if (features$
|
|
107
|
+
if (features$2.isEnabled("cms-content-releases")) {
|
|
75
108
|
await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
|
109
|
+
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
|
|
110
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
114
|
+
return strapi2.plugin("content-releases").service(name);
|
|
115
|
+
};
|
|
116
|
+
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
117
|
+
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
118
|
+
if (features$1.isEnabled("cms-content-releases")) {
|
|
119
|
+
strapi2.db.lifecycles.subscribe({
|
|
120
|
+
afterDelete(event) {
|
|
121
|
+
const { model, result } = event;
|
|
122
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
123
|
+
const { id } = result;
|
|
124
|
+
strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
125
|
+
where: {
|
|
126
|
+
target_type: model.uid,
|
|
127
|
+
target_id: id
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* deleteMany hook doesn't return the deleted entries ids
|
|
134
|
+
* so we need to fetch them before deleting the entries to save the ids on our state
|
|
135
|
+
*/
|
|
136
|
+
async beforeDeleteMany(event) {
|
|
137
|
+
const { model, params } = event;
|
|
138
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
139
|
+
const { where } = params;
|
|
140
|
+
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
141
|
+
event.state.entriesToDelete = entriesToDelete;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
/**
|
|
145
|
+
* We delete the release actions related to deleted entries
|
|
146
|
+
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
147
|
+
*/
|
|
148
|
+
async afterDeleteMany(event) {
|
|
149
|
+
const { model, state } = event;
|
|
150
|
+
const entriesToDelete = state.entriesToDelete;
|
|
151
|
+
if (entriesToDelete) {
|
|
152
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
153
|
+
where: {
|
|
154
|
+
target_type: model.uid,
|
|
155
|
+
target_id: {
|
|
156
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
164
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
165
|
+
strapi2.log.error(
|
|
166
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
167
|
+
);
|
|
168
|
+
throw err;
|
|
169
|
+
});
|
|
170
|
+
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
171
|
+
strapi2.webhookStore.addAllowedEvent(key, value);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
const destroy = async ({ strapi: strapi2 }) => {
|
|
177
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
178
|
+
const scheduledJobs = getService("scheduling", {
|
|
179
|
+
strapi: strapi2
|
|
180
|
+
}).getAll();
|
|
181
|
+
for (const [, job] of scheduledJobs) {
|
|
182
|
+
job.cancel();
|
|
183
|
+
}
|
|
76
184
|
}
|
|
77
185
|
};
|
|
78
186
|
const schema$1 = {
|
|
@@ -101,6 +209,12 @@ const schema$1 = {
|
|
|
101
209
|
releasedAt: {
|
|
102
210
|
type: "datetime"
|
|
103
211
|
},
|
|
212
|
+
scheduledAt: {
|
|
213
|
+
type: "datetime"
|
|
214
|
+
},
|
|
215
|
+
timezone: {
|
|
216
|
+
type: "string"
|
|
217
|
+
},
|
|
104
218
|
actions: {
|
|
105
219
|
type: "relation",
|
|
106
220
|
relation: "oneToMany",
|
|
@@ -163,327 +277,516 @@ const contentTypes = {
|
|
|
163
277
|
release: release$1,
|
|
164
278
|
"release-action": releaseAction$1
|
|
165
279
|
};
|
|
166
|
-
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
167
|
-
return strapi2.plugin("content-releases").service(name);
|
|
168
|
-
};
|
|
169
280
|
const getGroupName = (queryValue) => {
|
|
170
281
|
switch (queryValue) {
|
|
171
282
|
case "contentType":
|
|
172
|
-
return "
|
|
283
|
+
return "contentType.displayName";
|
|
173
284
|
case "action":
|
|
174
285
|
return "type";
|
|
175
286
|
case "locale":
|
|
176
|
-
return ___default.default.getOr("No locale", "
|
|
287
|
+
return ___default.default.getOr("No locale", "locale.name");
|
|
177
288
|
default:
|
|
178
|
-
return "
|
|
289
|
+
return "contentType.displayName";
|
|
179
290
|
}
|
|
180
291
|
};
|
|
181
|
-
const createReleaseService = ({ strapi: strapi2 }) =>
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
292
|
+
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
293
|
+
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
294
|
+
strapi2.eventHub.emit(event, {
|
|
295
|
+
isPublished,
|
|
296
|
+
error,
|
|
297
|
+
release: release2
|
|
186
298
|
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
299
|
+
};
|
|
300
|
+
return {
|
|
301
|
+
async create(releaseData, { user }) {
|
|
302
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
|
|
303
|
+
const {
|
|
304
|
+
validatePendingReleasesLimit,
|
|
305
|
+
validateUniqueNameForPendingRelease,
|
|
306
|
+
validateScheduledAtIsLaterThanNow
|
|
307
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
308
|
+
await Promise.all([
|
|
309
|
+
validatePendingReleasesLimit(),
|
|
310
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
311
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
312
|
+
]);
|
|
313
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
314
|
+
data: releaseWithCreatorFields
|
|
315
|
+
});
|
|
316
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
|
|
317
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
318
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
202
319
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
320
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
321
|
+
return release2;
|
|
322
|
+
},
|
|
323
|
+
async findOne(id, query = {}) {
|
|
324
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
325
|
+
...query
|
|
326
|
+
});
|
|
327
|
+
return release2;
|
|
328
|
+
},
|
|
329
|
+
findPage(query) {
|
|
330
|
+
return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
|
|
331
|
+
...query,
|
|
332
|
+
populate: {
|
|
333
|
+
actions: {
|
|
334
|
+
// @ts-expect-error Ignore missing properties
|
|
335
|
+
count: true
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
},
|
|
340
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
341
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
342
|
+
where: {
|
|
343
|
+
actions: {
|
|
344
|
+
target_type: contentTypeUid,
|
|
345
|
+
target_id: entryId
|
|
346
|
+
},
|
|
347
|
+
releasedAt: {
|
|
348
|
+
$null: true
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
populate: {
|
|
352
|
+
// Filter the action to get only the content type entry
|
|
353
|
+
actions: {
|
|
354
|
+
where: {
|
|
222
355
|
target_type: contentTypeUid,
|
|
223
356
|
target_id: entryId
|
|
224
357
|
}
|
|
225
358
|
}
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
actions: null
|
|
229
359
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
+
};
|
|
369
|
+
}
|
|
370
|
+
return release2;
|
|
371
|
+
});
|
|
372
|
+
},
|
|
373
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
374
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
235
375
|
where: {
|
|
236
|
-
|
|
237
|
-
|
|
376
|
+
releasedAt: {
|
|
377
|
+
$null: true
|
|
378
|
+
},
|
|
379
|
+
actions: {
|
|
380
|
+
target_type: contentTypeUid,
|
|
381
|
+
target_id: entryId
|
|
382
|
+
}
|
|
238
383
|
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
384
|
+
});
|
|
385
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
386
|
+
where: {
|
|
387
|
+
$or: [
|
|
388
|
+
{
|
|
389
|
+
id: {
|
|
390
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
actions: null
|
|
395
|
+
}
|
|
396
|
+
],
|
|
397
|
+
releasedAt: {
|
|
398
|
+
$null: true
|
|
399
|
+
}
|
|
246
400
|
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
401
|
+
});
|
|
402
|
+
return releases.map((release2) => {
|
|
403
|
+
if (release2.actions?.length) {
|
|
404
|
+
const [actionForEntry] = release2.actions;
|
|
405
|
+
delete release2.actions;
|
|
406
|
+
return {
|
|
407
|
+
...release2,
|
|
408
|
+
action: actionForEntry
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
return release2;
|
|
412
|
+
});
|
|
413
|
+
},
|
|
414
|
+
async update(id, releaseData, { user }) {
|
|
415
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(
|
|
416
|
+
releaseData
|
|
417
|
+
);
|
|
418
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
419
|
+
"release-validation",
|
|
420
|
+
{ strapi: strapi2 }
|
|
421
|
+
);
|
|
422
|
+
await Promise.all([
|
|
423
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
424
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
425
|
+
]);
|
|
426
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
427
|
+
if (!release2) {
|
|
428
|
+
throw new utils.errors.NotFoundError(`No release found for id ${id}`);
|
|
250
429
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (release2.actions?.length) {
|
|
254
|
-
const [actionForEntry] = release2.actions;
|
|
255
|
-
delete release2.actions;
|
|
256
|
-
return {
|
|
257
|
-
...release2,
|
|
258
|
-
action: actionForEntry
|
|
259
|
-
};
|
|
430
|
+
if (release2.releasedAt) {
|
|
431
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
260
432
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
return release2;
|
|
278
|
-
},
|
|
279
|
-
async createAction(releaseId, action) {
|
|
280
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
281
|
-
strapi: strapi2
|
|
282
|
-
});
|
|
283
|
-
await Promise.all([
|
|
284
|
-
validateEntryContentType(action.entry.contentType),
|
|
285
|
-
validateUniqueEntry(releaseId, action)
|
|
286
|
-
]);
|
|
287
|
-
const { entry, type } = action;
|
|
288
|
-
return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
289
|
-
data: {
|
|
290
|
-
type,
|
|
291
|
-
contentType: entry.contentType,
|
|
292
|
-
locale: entry.locale,
|
|
293
|
-
entry: {
|
|
294
|
-
id: entry.id,
|
|
295
|
-
__type: entry.contentType,
|
|
296
|
-
__pivot: { field: "entry" }
|
|
297
|
-
},
|
|
298
|
-
release: releaseId
|
|
299
|
-
},
|
|
300
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
301
|
-
});
|
|
302
|
-
},
|
|
303
|
-
async findActions(releaseId, query) {
|
|
304
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
305
|
-
fields: ["id"]
|
|
306
|
-
});
|
|
307
|
-
if (!release2) {
|
|
308
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
309
|
-
}
|
|
310
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
311
|
-
...query,
|
|
312
|
-
populate: {
|
|
313
|
-
entry: true
|
|
314
|
-
},
|
|
315
|
-
filters: {
|
|
316
|
-
release: releaseId
|
|
433
|
+
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
434
|
+
/*
|
|
435
|
+
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
436
|
+
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
437
|
+
*/
|
|
438
|
+
// @ts-expect-error see above
|
|
439
|
+
data: releaseWithCreatorFields
|
|
440
|
+
});
|
|
441
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
442
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
443
|
+
if (releaseData.scheduledAt) {
|
|
444
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
445
|
+
} else if (release2.scheduledAt) {
|
|
446
|
+
schedulingService.cancel(id);
|
|
447
|
+
}
|
|
317
448
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
449
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
450
|
+
return updatedRelease;
|
|
451
|
+
},
|
|
452
|
+
async createAction(releaseId, action) {
|
|
453
|
+
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
454
|
+
strapi: strapi2
|
|
455
|
+
});
|
|
456
|
+
await Promise.all([
|
|
457
|
+
validateEntryContentType(action.entry.contentType),
|
|
458
|
+
validateUniqueEntry(releaseId, action)
|
|
459
|
+
]);
|
|
460
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
461
|
+
if (!release2) {
|
|
462
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
327
463
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
...action,
|
|
342
|
-
entry: {
|
|
343
|
-
id: action.entry.id,
|
|
344
|
-
contentType: {
|
|
345
|
-
displayName,
|
|
346
|
-
mainFieldValue: action.entry[mainField]
|
|
464
|
+
if (release2.releasedAt) {
|
|
465
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
466
|
+
}
|
|
467
|
+
const { entry, type } = action;
|
|
468
|
+
return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
469
|
+
data: {
|
|
470
|
+
type,
|
|
471
|
+
contentType: entry.contentType,
|
|
472
|
+
locale: entry.locale,
|
|
473
|
+
entry: {
|
|
474
|
+
id: entry.id,
|
|
475
|
+
__type: entry.contentType,
|
|
476
|
+
__pivot: { field: "entry" }
|
|
347
477
|
},
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
};
|
|
352
|
-
});
|
|
353
|
-
const groupName = getGroupName(groupBy);
|
|
354
|
-
return ___default.default.groupBy(groupName)(formattedData);
|
|
355
|
-
},
|
|
356
|
-
async getContentTypesDataForActions(contentTypesUids) {
|
|
357
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
358
|
-
const contentTypesData = {};
|
|
359
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
360
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
361
|
-
uid: contentTypeUid
|
|
478
|
+
release: releaseId
|
|
479
|
+
},
|
|
480
|
+
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
362
481
|
});
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
async delete(releaseId) {
|
|
371
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
372
|
-
populate: {
|
|
373
|
-
actions: {
|
|
374
|
-
fields: ["id"]
|
|
375
|
-
}
|
|
482
|
+
},
|
|
483
|
+
async findActions(releaseId, query) {
|
|
484
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
485
|
+
fields: ["id"]
|
|
486
|
+
});
|
|
487
|
+
if (!release2) {
|
|
488
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
376
489
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
383
|
-
}
|
|
384
|
-
await strapi2.db.transaction(async () => {
|
|
385
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
386
|
-
where: {
|
|
387
|
-
id: {
|
|
388
|
-
$in: release2.actions.map((action) => action.id)
|
|
490
|
+
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
491
|
+
...query,
|
|
492
|
+
populate: {
|
|
493
|
+
entry: {
|
|
494
|
+
populate: "*"
|
|
389
495
|
}
|
|
496
|
+
},
|
|
497
|
+
filters: {
|
|
498
|
+
release: releaseId
|
|
390
499
|
}
|
|
391
500
|
});
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
501
|
+
},
|
|
502
|
+
async countActions(query) {
|
|
503
|
+
return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
|
|
504
|
+
},
|
|
505
|
+
async groupActions(actions, groupBy) {
|
|
506
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
507
|
+
if (!acc.includes(action.contentType)) {
|
|
508
|
+
acc.push(action.contentType);
|
|
509
|
+
}
|
|
510
|
+
return acc;
|
|
511
|
+
}, []);
|
|
512
|
+
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
|
|
513
|
+
contentTypeUids
|
|
514
|
+
);
|
|
515
|
+
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
516
|
+
const formattedData = actions.map((action) => {
|
|
517
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
518
|
+
return {
|
|
519
|
+
...action,
|
|
520
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
521
|
+
contentType: {
|
|
522
|
+
displayName,
|
|
523
|
+
mainFieldValue: action.entry[mainField],
|
|
524
|
+
uid: action.contentType
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
});
|
|
528
|
+
const groupName = getGroupName(groupBy);
|
|
529
|
+
return ___default.default.groupBy(groupName)(formattedData);
|
|
530
|
+
},
|
|
531
|
+
async getLocalesDataForActions() {
|
|
532
|
+
if (!strapi2.plugin("i18n")) {
|
|
533
|
+
return {};
|
|
534
|
+
}
|
|
535
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
536
|
+
return allLocales.reduce((acc, locale) => {
|
|
537
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
538
|
+
return acc;
|
|
539
|
+
}, {});
|
|
540
|
+
},
|
|
541
|
+
async getContentTypesDataForActions(contentTypesUids) {
|
|
542
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
543
|
+
const contentTypesData = {};
|
|
544
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
545
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
546
|
+
uid: contentTypeUid
|
|
547
|
+
});
|
|
548
|
+
contentTypesData[contentTypeUid] = {
|
|
549
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
550
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
return contentTypesData;
|
|
554
|
+
},
|
|
555
|
+
getContentTypeModelsFromActions(actions) {
|
|
556
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
557
|
+
if (!acc.includes(action.contentType)) {
|
|
558
|
+
acc.push(action.contentType);
|
|
559
|
+
}
|
|
560
|
+
return acc;
|
|
561
|
+
}, []);
|
|
562
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
563
|
+
(acc, contentTypeUid) => {
|
|
564
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
565
|
+
return acc;
|
|
566
|
+
},
|
|
567
|
+
{}
|
|
568
|
+
);
|
|
569
|
+
return contentTypeModelsMap;
|
|
570
|
+
},
|
|
571
|
+
async getAllComponents() {
|
|
572
|
+
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
573
|
+
const components = await contentManagerComponentsService.findAllComponents();
|
|
574
|
+
const componentsMap = components.reduce(
|
|
575
|
+
(acc, component) => {
|
|
576
|
+
acc[component.uid] = component;
|
|
577
|
+
return acc;
|
|
578
|
+
},
|
|
579
|
+
{}
|
|
580
|
+
);
|
|
581
|
+
return componentsMap;
|
|
582
|
+
},
|
|
583
|
+
async delete(releaseId) {
|
|
584
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
401
585
|
populate: {
|
|
402
586
|
actions: {
|
|
403
|
-
|
|
404
|
-
entry: true
|
|
405
|
-
}
|
|
587
|
+
fields: ["id"]
|
|
406
588
|
}
|
|
407
589
|
}
|
|
590
|
+
});
|
|
591
|
+
if (!release2) {
|
|
592
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
408
593
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
412
|
-
}
|
|
413
|
-
if (releaseWithPopulatedActionEntries.releasedAt) {
|
|
414
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
415
|
-
}
|
|
416
|
-
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
417
|
-
throw new utils.errors.ValidationError("No entries to publish");
|
|
418
|
-
}
|
|
419
|
-
const actions = {};
|
|
420
|
-
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
421
|
-
const contentTypeUid = action.contentType;
|
|
422
|
-
if (!actions[contentTypeUid]) {
|
|
423
|
-
actions[contentTypeUid] = {
|
|
424
|
-
publish: [],
|
|
425
|
-
unpublish: []
|
|
426
|
-
};
|
|
594
|
+
if (release2.releasedAt) {
|
|
595
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
427
596
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
597
|
+
await strapi2.db.transaction(async () => {
|
|
598
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
599
|
+
where: {
|
|
600
|
+
id: {
|
|
601
|
+
$in: release2.actions.map((action) => action.id)
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
|
606
|
+
});
|
|
607
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
|
|
608
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
609
|
+
await schedulingService.cancel(release2.id);
|
|
432
610
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
611
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
612
|
+
return release2;
|
|
613
|
+
},
|
|
614
|
+
async publish(releaseId) {
|
|
615
|
+
try {
|
|
616
|
+
const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
|
|
617
|
+
RELEASE_MODEL_UID,
|
|
618
|
+
releaseId,
|
|
619
|
+
{
|
|
620
|
+
populate: {
|
|
621
|
+
actions: {
|
|
622
|
+
populate: {
|
|
623
|
+
entry: {
|
|
624
|
+
fields: ["id"]
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
);
|
|
631
|
+
if (!releaseWithPopulatedActionEntries) {
|
|
632
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
633
|
+
}
|
|
634
|
+
if (releaseWithPopulatedActionEntries.releasedAt) {
|
|
635
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
636
|
+
}
|
|
637
|
+
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
638
|
+
throw new utils.errors.ValidationError("No entries to publish");
|
|
440
639
|
}
|
|
441
|
-
|
|
442
|
-
|
|
640
|
+
const collectionTypeActions = {};
|
|
641
|
+
const singleTypeActions = [];
|
|
642
|
+
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
643
|
+
const contentTypeUid = action.contentType;
|
|
644
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
645
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
646
|
+
collectionTypeActions[contentTypeUid] = {
|
|
647
|
+
entriestoPublishIds: [],
|
|
648
|
+
entriesToUnpublishIds: []
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
if (action.type === "publish") {
|
|
652
|
+
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
653
|
+
} else {
|
|
654
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
655
|
+
}
|
|
656
|
+
} else {
|
|
657
|
+
singleTypeActions.push({
|
|
658
|
+
uid: contentTypeUid,
|
|
659
|
+
action: action.type,
|
|
660
|
+
id: action.entry.id
|
|
661
|
+
});
|
|
662
|
+
}
|
|
443
663
|
}
|
|
664
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
665
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
666
|
+
await strapi2.db.transaction(async () => {
|
|
667
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
668
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
669
|
+
const entry = await strapi2.entityService.findOne(uid, id, { populate });
|
|
670
|
+
try {
|
|
671
|
+
if (action === "publish") {
|
|
672
|
+
await entityManagerService.publish(entry, uid);
|
|
673
|
+
} else {
|
|
674
|
+
await entityManagerService.unpublish(entry, uid);
|
|
675
|
+
}
|
|
676
|
+
} catch (error) {
|
|
677
|
+
if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
|
|
678
|
+
} else {
|
|
679
|
+
throw error;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
684
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
685
|
+
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
686
|
+
const entriesToPublish = await strapi2.entityService.findMany(
|
|
687
|
+
contentTypeUid,
|
|
688
|
+
{
|
|
689
|
+
filters: {
|
|
690
|
+
id: {
|
|
691
|
+
$in: entriestoPublishIds
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
populate
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(
|
|
698
|
+
contentTypeUid,
|
|
699
|
+
{
|
|
700
|
+
filters: {
|
|
701
|
+
id: {
|
|
702
|
+
$in: entriesToUnpublishIds
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
populate
|
|
706
|
+
}
|
|
707
|
+
);
|
|
708
|
+
if (entriesToPublish.length > 0) {
|
|
709
|
+
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
710
|
+
}
|
|
711
|
+
if (entriesToUnpublish.length > 0) {
|
|
712
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
|
|
717
|
+
data: {
|
|
718
|
+
/*
|
|
719
|
+
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
|
|
720
|
+
*/
|
|
721
|
+
// @ts-expect-error see above
|
|
722
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
723
|
+
},
|
|
724
|
+
populate: {
|
|
725
|
+
actions: {
|
|
726
|
+
// @ts-expect-error is not expecting count but it is working
|
|
727
|
+
count: true
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
732
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
733
|
+
isPublished: true,
|
|
734
|
+
release: release2
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
738
|
+
return release2;
|
|
739
|
+
} catch (error) {
|
|
740
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
741
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
742
|
+
isPublished: false,
|
|
743
|
+
error
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
throw error;
|
|
444
747
|
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
748
|
+
},
|
|
749
|
+
async updateAction(actionId, releaseId, update) {
|
|
750
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
751
|
+
where: {
|
|
752
|
+
id: actionId,
|
|
753
|
+
release: {
|
|
754
|
+
id: releaseId,
|
|
755
|
+
releasedAt: {
|
|
756
|
+
$null: true
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
data: update
|
|
761
|
+
});
|
|
762
|
+
if (!updatedAction) {
|
|
763
|
+
throw new utils.errors.NotFoundError(
|
|
764
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
765
|
+
);
|
|
453
766
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
)
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
async deleteAction(actionId, releaseId) {
|
|
473
|
-
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
474
|
-
where: {
|
|
475
|
-
id: actionId,
|
|
476
|
-
release: releaseId
|
|
767
|
+
return updatedAction;
|
|
768
|
+
},
|
|
769
|
+
async deleteAction(actionId, releaseId) {
|
|
770
|
+
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
771
|
+
where: {
|
|
772
|
+
id: actionId,
|
|
773
|
+
release: {
|
|
774
|
+
id: releaseId,
|
|
775
|
+
releasedAt: {
|
|
776
|
+
$null: true
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
if (!deletedAction) {
|
|
782
|
+
throw new utils.errors.NotFoundError(
|
|
783
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
784
|
+
);
|
|
477
785
|
}
|
|
478
|
-
|
|
479
|
-
if (!deletedAction) {
|
|
480
|
-
throw new utils.errors.NotFoundError(
|
|
481
|
-
`Action with id ${actionId} not found in release with id ${releaseId}`
|
|
482
|
-
);
|
|
786
|
+
return deletedAction;
|
|
483
787
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
});
|
|
788
|
+
};
|
|
789
|
+
};
|
|
487
790
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
488
791
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
489
792
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
@@ -511,11 +814,120 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
511
814
|
`Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
|
|
512
815
|
);
|
|
513
816
|
}
|
|
817
|
+
},
|
|
818
|
+
async validatePendingReleasesLimit() {
|
|
819
|
+
const maximumPendingReleases = (
|
|
820
|
+
// @ts-expect-error - options is not typed into features
|
|
821
|
+
EE__default.default.features.get("cms-content-releases")?.options?.maximumReleases || 3
|
|
822
|
+
);
|
|
823
|
+
const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
|
|
824
|
+
filters: {
|
|
825
|
+
releasedAt: {
|
|
826
|
+
$null: true
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
if (pendingReleasesCount >= maximumPendingReleases) {
|
|
831
|
+
throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
|
|
832
|
+
}
|
|
833
|
+
},
|
|
834
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
835
|
+
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
836
|
+
filters: {
|
|
837
|
+
releasedAt: {
|
|
838
|
+
$null: true
|
|
839
|
+
},
|
|
840
|
+
name,
|
|
841
|
+
...id && { id: { $ne: id } }
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
const isNameUnique = pendingReleases.length === 0;
|
|
845
|
+
if (!isNameUnique) {
|
|
846
|
+
throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
850
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
851
|
+
throw new utils.errors.ValidationError("Scheduled at must be later than now");
|
|
852
|
+
}
|
|
514
853
|
}
|
|
515
854
|
});
|
|
516
|
-
const
|
|
855
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
856
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
857
|
+
return {
|
|
858
|
+
async set(releaseId, scheduleDate) {
|
|
859
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
860
|
+
if (!release2) {
|
|
861
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
862
|
+
}
|
|
863
|
+
const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
|
|
864
|
+
try {
|
|
865
|
+
await getService("release").publish(releaseId);
|
|
866
|
+
} catch (error) {
|
|
867
|
+
}
|
|
868
|
+
this.cancel(releaseId);
|
|
869
|
+
});
|
|
870
|
+
if (scheduledJobs.has(releaseId)) {
|
|
871
|
+
this.cancel(releaseId);
|
|
872
|
+
}
|
|
873
|
+
scheduledJobs.set(releaseId, job);
|
|
874
|
+
return scheduledJobs;
|
|
875
|
+
},
|
|
876
|
+
cancel(releaseId) {
|
|
877
|
+
if (scheduledJobs.has(releaseId)) {
|
|
878
|
+
scheduledJobs.get(releaseId).cancel();
|
|
879
|
+
scheduledJobs.delete(releaseId);
|
|
880
|
+
}
|
|
881
|
+
return scheduledJobs;
|
|
882
|
+
},
|
|
883
|
+
getAll() {
|
|
884
|
+
return scheduledJobs;
|
|
885
|
+
},
|
|
886
|
+
/**
|
|
887
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
888
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
889
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
890
|
+
*/
|
|
891
|
+
async syncFromDatabase() {
|
|
892
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
893
|
+
where: {
|
|
894
|
+
scheduledAt: {
|
|
895
|
+
$gte: /* @__PURE__ */ new Date()
|
|
896
|
+
},
|
|
897
|
+
releasedAt: null
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
for (const release2 of releases) {
|
|
901
|
+
this.set(release2.id, release2.scheduledAt);
|
|
902
|
+
}
|
|
903
|
+
return scheduledJobs;
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
};
|
|
907
|
+
const services = {
|
|
908
|
+
release: createReleaseService,
|
|
909
|
+
"release-validation": createReleaseValidationService,
|
|
910
|
+
...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
|
|
911
|
+
};
|
|
517
912
|
const RELEASE_SCHEMA = yup__namespace.object().shape({
|
|
518
|
-
name: yup__namespace.string().trim().required()
|
|
913
|
+
name: yup__namespace.string().trim().required(),
|
|
914
|
+
scheduledAt: yup__namespace.string().nullable(),
|
|
915
|
+
isScheduled: yup__namespace.boolean().optional(),
|
|
916
|
+
time: yup__namespace.string().when("isScheduled", {
|
|
917
|
+
is: true,
|
|
918
|
+
then: yup__namespace.string().trim().required(),
|
|
919
|
+
otherwise: yup__namespace.string().nullable()
|
|
920
|
+
}),
|
|
921
|
+
timezone: yup__namespace.string().when("isScheduled", {
|
|
922
|
+
is: true,
|
|
923
|
+
then: yup__namespace.string().required().nullable(),
|
|
924
|
+
otherwise: yup__namespace.string().nullable()
|
|
925
|
+
}),
|
|
926
|
+
date: yup__namespace.string().when("isScheduled", {
|
|
927
|
+
is: true,
|
|
928
|
+
then: yup__namespace.string().required().nullable(),
|
|
929
|
+
otherwise: yup__namespace.string().nullable()
|
|
930
|
+
})
|
|
519
931
|
}).required().noUnknown();
|
|
520
932
|
const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
|
|
521
933
|
const releaseController = {
|
|
@@ -532,9 +944,7 @@ const releaseController = {
|
|
|
532
944
|
const contentTypeUid = query.contentTypeUid;
|
|
533
945
|
const entryId = query.entryId;
|
|
534
946
|
const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
|
|
535
|
-
const data = await releaseService.
|
|
536
|
-
hasEntryAttached
|
|
537
|
-
});
|
|
947
|
+
const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
|
|
538
948
|
ctx.body = { data };
|
|
539
949
|
} else {
|
|
540
950
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
@@ -557,19 +967,18 @@ const releaseController = {
|
|
|
557
967
|
const id = ctx.params.id;
|
|
558
968
|
const releaseService = getService("release", { strapi });
|
|
559
969
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
});
|
|
564
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
970
|
+
if (!release2) {
|
|
971
|
+
throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
|
|
972
|
+
}
|
|
565
973
|
const count = await releaseService.countActions({
|
|
566
974
|
filters: {
|
|
567
975
|
release: id
|
|
568
976
|
}
|
|
569
977
|
});
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
978
|
+
const sanitizedRelease = {
|
|
979
|
+
...release2,
|
|
980
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
981
|
+
};
|
|
573
982
|
const data = {
|
|
574
983
|
...sanitizedRelease,
|
|
575
984
|
actions: {
|
|
@@ -622,8 +1031,27 @@ const releaseController = {
|
|
|
622
1031
|
const id = ctx.params.id;
|
|
623
1032
|
const releaseService = getService("release", { strapi });
|
|
624
1033
|
const release2 = await releaseService.publish(id, { user });
|
|
1034
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1035
|
+
releaseService.countActions({
|
|
1036
|
+
filters: {
|
|
1037
|
+
release: id,
|
|
1038
|
+
type: "publish"
|
|
1039
|
+
}
|
|
1040
|
+
}),
|
|
1041
|
+
releaseService.countActions({
|
|
1042
|
+
filters: {
|
|
1043
|
+
release: id,
|
|
1044
|
+
type: "unpublish"
|
|
1045
|
+
}
|
|
1046
|
+
})
|
|
1047
|
+
]);
|
|
625
1048
|
ctx.body = {
|
|
626
|
-
data: release2
|
|
1049
|
+
data: release2,
|
|
1050
|
+
meta: {
|
|
1051
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1052
|
+
totalPublishedEntries: countPublishActions,
|
|
1053
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1054
|
+
}
|
|
627
1055
|
};
|
|
628
1056
|
}
|
|
629
1057
|
};
|
|
@@ -662,11 +1090,30 @@ const releaseActionController = {
|
|
|
662
1090
|
sort: query.groupBy === "action" ? "type" : query.groupBy,
|
|
663
1091
|
...query
|
|
664
1092
|
});
|
|
665
|
-
const
|
|
1093
|
+
const contentTypeOutputSanitizers = results.reduce((acc, action) => {
|
|
1094
|
+
if (acc[action.contentType]) {
|
|
1095
|
+
return acc;
|
|
1096
|
+
}
|
|
1097
|
+
const contentTypePermissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
|
1098
|
+
ability: ctx.state.userAbility,
|
|
1099
|
+
model: action.contentType
|
|
1100
|
+
});
|
|
1101
|
+
acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
|
|
1102
|
+
return acc;
|
|
1103
|
+
}, {});
|
|
1104
|
+
const sanitizedResults = await utils.mapAsync(results, async (action) => ({
|
|
1105
|
+
...action,
|
|
1106
|
+
entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
|
|
1107
|
+
}));
|
|
1108
|
+
const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
|
|
1109
|
+
const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
|
|
1110
|
+
const components = await releaseService.getAllComponents();
|
|
666
1111
|
ctx.body = {
|
|
667
1112
|
data: groupedData,
|
|
668
1113
|
meta: {
|
|
669
|
-
pagination
|
|
1114
|
+
pagination,
|
|
1115
|
+
contentTypes: contentTypes2,
|
|
1116
|
+
components
|
|
670
1117
|
}
|
|
671
1118
|
};
|
|
672
1119
|
},
|
|
@@ -688,10 +1135,8 @@ const releaseActionController = {
|
|
|
688
1135
|
async delete(ctx) {
|
|
689
1136
|
const actionId = ctx.params.actionId;
|
|
690
1137
|
const releaseId = ctx.params.releaseId;
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
releaseId
|
|
694
|
-
);
|
|
1138
|
+
const releaseService = getService("release", { strapi });
|
|
1139
|
+
const deletedReleaseAction = await releaseService.deleteAction(actionId, releaseId);
|
|
695
1140
|
ctx.body = {
|
|
696
1141
|
data: deletedReleaseAction
|
|
697
1142
|
};
|
|
@@ -874,9 +1319,11 @@ const routes = {
|
|
|
874
1319
|
};
|
|
875
1320
|
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
876
1321
|
const getPlugin = () => {
|
|
877
|
-
if (features.isEnabled("cms-content-releases")
|
|
1322
|
+
if (features.isEnabled("cms-content-releases")) {
|
|
878
1323
|
return {
|
|
879
1324
|
register,
|
|
1325
|
+
bootstrap,
|
|
1326
|
+
destroy,
|
|
880
1327
|
contentTypes,
|
|
881
1328
|
services,
|
|
882
1329
|
controllers,
|