@strapi/content-releases 0.0.0-next.6d59515520a3850456f256fb0e4c54b75054ddf4 → 0.0.0-next.73143c28059b343ba62d98c29672ab114562fbbc
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-L1jSxCiL.mjs → App-g3vtS2Wa.mjs} +524 -252
- package/dist/_chunks/App-g3vtS2Wa.mjs.map +1 -0
- package/dist/_chunks/{App-_20W9dYa.js → App-lnXbSPgp.js} +520 -247
- package/dist/_chunks/App-lnXbSPgp.js.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-MyLPoISH.mjs → en-WuuhP6Bn.mjs} +21 -6
- package/dist/_chunks/en-WuuhP6Bn.mjs.map +1 -0
- package/dist/_chunks/{en-gYDqKYFd.js → en-gcJJ5htG.js} +21 -6
- package/dist/_chunks/en-gcJJ5htG.js.map +1 -0
- package/dist/_chunks/{index-KJa1Rb5F.js → index-ItlgrLcr.js} +158 -32
- package/dist/_chunks/index-ItlgrLcr.js.map +1 -0
- package/dist/_chunks/{index-c4zRX_sg.mjs → index-uGex_IIQ.mjs} +163 -37
- package/dist/_chunks/index-uGex_IIQ.mjs.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +2 -2
- package/dist/server/index.js +887 -398
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +886 -398
- package/dist/server/index.mjs.map +1 -1
- package/package.json +14 -11
- package/dist/_chunks/App-L1jSxCiL.mjs.map +0 -1
- package/dist/_chunks/App-_20W9dYa.js.map +0 -1
- package/dist/_chunks/en-MyLPoISH.mjs.map +0 -1
- package/dist/_chunks/en-gYDqKYFd.js.map +0 -1
- package/dist/_chunks/index-KJa1Rb5F.js.map +0 -1
- package/dist/_chunks/index-c4zRX_sg.mjs.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const utils = require("@strapi/utils");
|
|
3
|
+
const isEqual = require("lodash/isEqual");
|
|
3
4
|
const lodash = require("lodash");
|
|
4
5
|
const _ = require("lodash/fp");
|
|
5
6
|
const EE = require("@strapi/strapi/dist/utils/ee");
|
|
7
|
+
const nodeSchedule = require("node-schedule");
|
|
6
8
|
const yup = require("yup");
|
|
7
9
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
8
10
|
function _interopNamespace(e) {
|
|
@@ -23,6 +25,7 @@ function _interopNamespace(e) {
|
|
|
23
25
|
n.default = e;
|
|
24
26
|
return Object.freeze(n);
|
|
25
27
|
}
|
|
28
|
+
const isEqual__default = /* @__PURE__ */ _interopDefault(isEqual);
|
|
26
29
|
const ___default = /* @__PURE__ */ _interopDefault(_);
|
|
27
30
|
const EE__default = /* @__PURE__ */ _interopDefault(EE);
|
|
28
31
|
const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
|
|
@@ -72,6 +75,32 @@ const ACTIONS = [
|
|
|
72
75
|
pluginName: "content-releases"
|
|
73
76
|
}
|
|
74
77
|
];
|
|
78
|
+
const ALLOWED_WEBHOOK_EVENTS = {
|
|
79
|
+
RELEASES_PUBLISH: "releases.publish"
|
|
80
|
+
};
|
|
81
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
82
|
+
return strapi2.plugin("content-releases").service(name);
|
|
83
|
+
};
|
|
84
|
+
const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
85
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
86
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
87
|
+
const entry = await strapi2.entityService.findOne(contentTypeUid, entryId, { populate });
|
|
88
|
+
return entry;
|
|
89
|
+
};
|
|
90
|
+
const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
91
|
+
try {
|
|
92
|
+
await strapi2.entityValidator.validateEntityCreation(
|
|
93
|
+
strapi2.getModel(contentTypeUid),
|
|
94
|
+
entry,
|
|
95
|
+
void 0,
|
|
96
|
+
// @ts-expect-error - FIXME: entity here is unnecessary
|
|
97
|
+
entry
|
|
98
|
+
);
|
|
99
|
+
return true;
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
75
104
|
async function deleteActionsOnDisableDraftAndPublish({
|
|
76
105
|
oldContentTypes,
|
|
77
106
|
contentTypes: contentTypes2
|
|
@@ -98,28 +127,151 @@ async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes:
|
|
|
98
127
|
});
|
|
99
128
|
}
|
|
100
129
|
}
|
|
130
|
+
async function migrateIsValidAndStatusReleases() {
|
|
131
|
+
const releasesWithoutStatus = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
132
|
+
where: {
|
|
133
|
+
status: null,
|
|
134
|
+
releasedAt: null
|
|
135
|
+
},
|
|
136
|
+
populate: {
|
|
137
|
+
actions: {
|
|
138
|
+
populate: {
|
|
139
|
+
entry: true
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
utils.mapAsync(releasesWithoutStatus, async (release2) => {
|
|
145
|
+
const actions = release2.actions;
|
|
146
|
+
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
147
|
+
for (const action of notValidatedActions) {
|
|
148
|
+
if (action.entry) {
|
|
149
|
+
const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
|
|
150
|
+
strapi
|
|
151
|
+
});
|
|
152
|
+
if (populatedEntry) {
|
|
153
|
+
const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
|
|
154
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
155
|
+
where: {
|
|
156
|
+
id: action.id
|
|
157
|
+
},
|
|
158
|
+
data: {
|
|
159
|
+
isEntryValid
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
166
|
+
});
|
|
167
|
+
const publishedReleases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
168
|
+
where: {
|
|
169
|
+
status: null,
|
|
170
|
+
releasedAt: {
|
|
171
|
+
$notNull: true
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
utils.mapAsync(publishedReleases, async (release2) => {
|
|
176
|
+
return strapi.db.query(RELEASE_MODEL_UID).update({
|
|
177
|
+
where: {
|
|
178
|
+
id: release2.id
|
|
179
|
+
},
|
|
180
|
+
data: {
|
|
181
|
+
status: "done"
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
187
|
+
if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
|
|
188
|
+
const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
|
|
189
|
+
(uid) => oldContentTypes[uid]?.options?.draftAndPublish
|
|
190
|
+
);
|
|
191
|
+
const releasesAffected = /* @__PURE__ */ new Set();
|
|
192
|
+
utils.mapAsync(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
|
|
193
|
+
const oldContentType = oldContentTypes[contentTypeUID];
|
|
194
|
+
const contentType = contentTypes2[contentTypeUID];
|
|
195
|
+
if (!isEqual__default.default(oldContentType?.attributes, contentType?.attributes)) {
|
|
196
|
+
const actions = await strapi.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
197
|
+
where: {
|
|
198
|
+
contentType: contentTypeUID
|
|
199
|
+
},
|
|
200
|
+
populate: {
|
|
201
|
+
entry: true,
|
|
202
|
+
release: true
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
await utils.mapAsync(actions, async (action) => {
|
|
206
|
+
if (action.entry) {
|
|
207
|
+
const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
|
|
208
|
+
strapi
|
|
209
|
+
});
|
|
210
|
+
if (populatedEntry) {
|
|
211
|
+
const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
|
|
212
|
+
strapi
|
|
213
|
+
});
|
|
214
|
+
releasesAffected.add(action.release.id);
|
|
215
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
216
|
+
where: {
|
|
217
|
+
id: action.id
|
|
218
|
+
},
|
|
219
|
+
data: {
|
|
220
|
+
isEntryValid
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}).then(() => {
|
|
228
|
+
utils.mapAsync(releasesAffected, async (releaseId) => {
|
|
229
|
+
return getService("release", { strapi }).updateReleaseStatus(releaseId);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
101
234
|
const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
|
|
102
235
|
const register = async ({ strapi: strapi2 }) => {
|
|
103
236
|
if (features$2.isEnabled("cms-content-releases")) {
|
|
104
237
|
await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
|
105
238
|
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
|
|
106
|
-
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
|
|
239
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
107
240
|
}
|
|
108
241
|
};
|
|
109
242
|
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
110
243
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
111
244
|
if (features$1.isEnabled("cms-content-releases")) {
|
|
245
|
+
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
246
|
+
(uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
|
|
247
|
+
);
|
|
112
248
|
strapi2.db.lifecycles.subscribe({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
249
|
+
models: contentTypesWithDraftAndPublish,
|
|
250
|
+
async afterDelete(event) {
|
|
251
|
+
try {
|
|
252
|
+
const { model, result } = event;
|
|
253
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
254
|
+
const { id } = result;
|
|
255
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
256
|
+
where: {
|
|
257
|
+
actions: {
|
|
258
|
+
target_type: model.uid,
|
|
259
|
+
target_id: id
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
264
|
+
where: {
|
|
265
|
+
target_type: model.uid,
|
|
266
|
+
target_id: id
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
for (const release2 of releases) {
|
|
270
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
121
271
|
}
|
|
122
|
-
}
|
|
272
|
+
}
|
|
273
|
+
} catch (error) {
|
|
274
|
+
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
123
275
|
}
|
|
124
276
|
},
|
|
125
277
|
/**
|
|
@@ -139,20 +291,98 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
139
291
|
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
140
292
|
*/
|
|
141
293
|
async afterDeleteMany(event) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
294
|
+
try {
|
|
295
|
+
const { model, state } = event;
|
|
296
|
+
const entriesToDelete = state.entriesToDelete;
|
|
297
|
+
if (entriesToDelete) {
|
|
298
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
299
|
+
where: {
|
|
300
|
+
actions: {
|
|
301
|
+
target_type: model.uid,
|
|
302
|
+
target_id: {
|
|
303
|
+
$in: entriesToDelete.map(
|
|
304
|
+
(entry) => entry.id
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
150
308
|
}
|
|
309
|
+
});
|
|
310
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
311
|
+
where: {
|
|
312
|
+
target_type: model.uid,
|
|
313
|
+
target_id: {
|
|
314
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
for (const release2 of releases) {
|
|
319
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
151
320
|
}
|
|
321
|
+
}
|
|
322
|
+
} catch (error) {
|
|
323
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
324
|
+
error
|
|
152
325
|
});
|
|
153
326
|
}
|
|
327
|
+
},
|
|
328
|
+
async afterUpdate(event) {
|
|
329
|
+
try {
|
|
330
|
+
const { model, result } = event;
|
|
331
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
332
|
+
const isEntryValid = await getEntryValidStatus(
|
|
333
|
+
model.uid,
|
|
334
|
+
result,
|
|
335
|
+
{
|
|
336
|
+
strapi: strapi2
|
|
337
|
+
}
|
|
338
|
+
);
|
|
339
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
340
|
+
where: {
|
|
341
|
+
target_type: model.uid,
|
|
342
|
+
target_id: result.id
|
|
343
|
+
},
|
|
344
|
+
data: {
|
|
345
|
+
isEntryValid
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
349
|
+
where: {
|
|
350
|
+
actions: {
|
|
351
|
+
target_type: model.uid,
|
|
352
|
+
target_id: result.id
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
for (const release2 of releases) {
|
|
357
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
} catch (error) {
|
|
361
|
+
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
362
|
+
}
|
|
154
363
|
}
|
|
155
364
|
});
|
|
365
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
366
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
367
|
+
strapi2.log.error(
|
|
368
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
369
|
+
);
|
|
370
|
+
throw err;
|
|
371
|
+
});
|
|
372
|
+
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
373
|
+
strapi2.webhookStore.addAllowedEvent(key, value);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
const destroy = async ({ strapi: strapi2 }) => {
|
|
379
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
380
|
+
const scheduledJobs = getService("scheduling", {
|
|
381
|
+
strapi: strapi2
|
|
382
|
+
}).getAll();
|
|
383
|
+
for (const [, job] of scheduledJobs) {
|
|
384
|
+
job.cancel();
|
|
385
|
+
}
|
|
156
386
|
}
|
|
157
387
|
};
|
|
158
388
|
const schema$1 = {
|
|
@@ -181,6 +411,17 @@ const schema$1 = {
|
|
|
181
411
|
releasedAt: {
|
|
182
412
|
type: "datetime"
|
|
183
413
|
},
|
|
414
|
+
scheduledAt: {
|
|
415
|
+
type: "datetime"
|
|
416
|
+
},
|
|
417
|
+
timezone: {
|
|
418
|
+
type: "string"
|
|
419
|
+
},
|
|
420
|
+
status: {
|
|
421
|
+
type: "enumeration",
|
|
422
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
423
|
+
required: true
|
|
424
|
+
},
|
|
184
425
|
actions: {
|
|
185
426
|
type: "relation",
|
|
186
427
|
relation: "oneToMany",
|
|
@@ -233,6 +474,9 @@ const schema = {
|
|
|
233
474
|
relation: "manyToOne",
|
|
234
475
|
target: RELEASE_MODEL_UID,
|
|
235
476
|
inversedBy: "actions"
|
|
477
|
+
},
|
|
478
|
+
isEntryValid: {
|
|
479
|
+
type: "boolean"
|
|
236
480
|
}
|
|
237
481
|
}
|
|
238
482
|
};
|
|
@@ -243,9 +487,6 @@ const contentTypes = {
|
|
|
243
487
|
release: release$1,
|
|
244
488
|
"release-action": releaseAction$1
|
|
245
489
|
};
|
|
246
|
-
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
247
|
-
return strapi2.plugin("content-releases").service(name);
|
|
248
|
-
};
|
|
249
490
|
const getGroupName = (queryValue) => {
|
|
250
491
|
switch (queryValue) {
|
|
251
492
|
case "contentType":
|
|
@@ -258,415 +499,563 @@ const getGroupName = (queryValue) => {
|
|
|
258
499
|
return "contentType.displayName";
|
|
259
500
|
}
|
|
260
501
|
};
|
|
261
|
-
const createReleaseService = ({ strapi: strapi2 }) =>
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
);
|
|
268
|
-
await Promise.all([
|
|
269
|
-
validatePendingReleasesLimit(),
|
|
270
|
-
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name)
|
|
271
|
-
]);
|
|
272
|
-
return strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
273
|
-
data: releaseWithCreatorFields
|
|
274
|
-
});
|
|
275
|
-
},
|
|
276
|
-
async findOne(id, query = {}) {
|
|
277
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
278
|
-
...query
|
|
502
|
+
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
503
|
+
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
504
|
+
strapi2.eventHub.emit(event, {
|
|
505
|
+
isPublished,
|
|
506
|
+
error,
|
|
507
|
+
release: release2
|
|
279
508
|
});
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
509
|
+
};
|
|
510
|
+
return {
|
|
511
|
+
async create(releaseData, { user }) {
|
|
512
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
|
|
513
|
+
const {
|
|
514
|
+
validatePendingReleasesLimit,
|
|
515
|
+
validateUniqueNameForPendingRelease,
|
|
516
|
+
validateScheduledAtIsLaterThanNow
|
|
517
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
518
|
+
await Promise.all([
|
|
519
|
+
validatePendingReleasesLimit(),
|
|
520
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
521
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
522
|
+
]);
|
|
523
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
524
|
+
data: {
|
|
525
|
+
...releaseWithCreatorFields,
|
|
526
|
+
status: "empty"
|
|
289
527
|
}
|
|
528
|
+
});
|
|
529
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
|
|
530
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
531
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
290
532
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
533
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
534
|
+
return release2;
|
|
535
|
+
},
|
|
536
|
+
async findOne(id, query = {}) {
|
|
537
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
538
|
+
...query
|
|
539
|
+
});
|
|
540
|
+
return release2;
|
|
541
|
+
},
|
|
542
|
+
findPage(query) {
|
|
543
|
+
return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
|
|
544
|
+
...query,
|
|
545
|
+
populate: {
|
|
546
|
+
actions: {
|
|
547
|
+
// @ts-expect-error Ignore missing properties
|
|
548
|
+
count: true
|
|
549
|
+
}
|
|
302
550
|
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
551
|
+
});
|
|
552
|
+
},
|
|
553
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
554
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
555
|
+
where: {
|
|
556
|
+
actions: {
|
|
308
557
|
target_type: contentTypeUid,
|
|
309
558
|
target_id: entryId
|
|
559
|
+
},
|
|
560
|
+
releasedAt: {
|
|
561
|
+
$null: true
|
|
310
562
|
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
return releases.map((release2) => {
|
|
315
|
-
if (release2.actions?.length) {
|
|
316
|
-
const [actionForEntry] = release2.actions;
|
|
317
|
-
delete release2.actions;
|
|
318
|
-
return {
|
|
319
|
-
...release2,
|
|
320
|
-
action: actionForEntry
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
return release2;
|
|
324
|
-
});
|
|
325
|
-
},
|
|
326
|
-
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
327
|
-
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
328
|
-
where: {
|
|
329
|
-
releasedAt: {
|
|
330
|
-
$null: true
|
|
331
563
|
},
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
339
|
-
where: {
|
|
340
|
-
$or: [
|
|
341
|
-
{
|
|
342
|
-
id: {
|
|
343
|
-
$notIn: releasesRelated.map((release2) => release2.id)
|
|
564
|
+
populate: {
|
|
565
|
+
// Filter the action to get only the content type entry
|
|
566
|
+
actions: {
|
|
567
|
+
where: {
|
|
568
|
+
target_type: contentTypeUid,
|
|
569
|
+
target_id: entryId
|
|
344
570
|
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
return releases.map((release2) => {
|
|
575
|
+
if (release2.actions?.length) {
|
|
576
|
+
const [actionForEntry] = release2.actions;
|
|
577
|
+
delete release2.actions;
|
|
578
|
+
return {
|
|
579
|
+
...release2,
|
|
580
|
+
action: actionForEntry
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
return release2;
|
|
584
|
+
});
|
|
585
|
+
},
|
|
586
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
587
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
588
|
+
where: {
|
|
589
|
+
releasedAt: {
|
|
590
|
+
$null: true
|
|
345
591
|
},
|
|
346
|
-
{
|
|
347
|
-
|
|
592
|
+
actions: {
|
|
593
|
+
target_type: contentTypeUid,
|
|
594
|
+
target_id: entryId
|
|
348
595
|
}
|
|
349
|
-
],
|
|
350
|
-
releasedAt: {
|
|
351
|
-
$null: true
|
|
352
596
|
}
|
|
597
|
+
});
|
|
598
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
599
|
+
where: {
|
|
600
|
+
$or: [
|
|
601
|
+
{
|
|
602
|
+
id: {
|
|
603
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
actions: null
|
|
608
|
+
}
|
|
609
|
+
],
|
|
610
|
+
releasedAt: {
|
|
611
|
+
$null: true
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
return releases.map((release2) => {
|
|
616
|
+
if (release2.actions?.length) {
|
|
617
|
+
const [actionForEntry] = release2.actions;
|
|
618
|
+
delete release2.actions;
|
|
619
|
+
return {
|
|
620
|
+
...release2,
|
|
621
|
+
action: actionForEntry
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
return release2;
|
|
625
|
+
});
|
|
626
|
+
},
|
|
627
|
+
async update(id, releaseData, { user }) {
|
|
628
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(
|
|
629
|
+
releaseData
|
|
630
|
+
);
|
|
631
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
632
|
+
"release-validation",
|
|
633
|
+
{ strapi: strapi2 }
|
|
634
|
+
);
|
|
635
|
+
await Promise.all([
|
|
636
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
637
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
638
|
+
]);
|
|
639
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
640
|
+
if (!release2) {
|
|
641
|
+
throw new utils.errors.NotFoundError(`No release found for id ${id}`);
|
|
353
642
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (release2.actions?.length) {
|
|
357
|
-
const [actionForEntry] = release2.actions;
|
|
358
|
-
delete release2.actions;
|
|
359
|
-
return {
|
|
360
|
-
...release2,
|
|
361
|
-
action: actionForEntry
|
|
362
|
-
};
|
|
643
|
+
if (release2.releasedAt) {
|
|
644
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
363
645
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
379
|
-
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
380
|
-
*/
|
|
381
|
-
// @ts-expect-error see above
|
|
382
|
-
data: releaseWithCreatorFields
|
|
383
|
-
});
|
|
384
|
-
return updatedRelease;
|
|
385
|
-
},
|
|
386
|
-
async createAction(releaseId, action) {
|
|
387
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
388
|
-
strapi: strapi2
|
|
389
|
-
});
|
|
390
|
-
await Promise.all([
|
|
391
|
-
validateEntryContentType(action.entry.contentType),
|
|
392
|
-
validateUniqueEntry(releaseId, action)
|
|
393
|
-
]);
|
|
394
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
395
|
-
if (!release2) {
|
|
396
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
397
|
-
}
|
|
398
|
-
if (release2.releasedAt) {
|
|
399
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
400
|
-
}
|
|
401
|
-
const { entry, type } = action;
|
|
402
|
-
return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
403
|
-
data: {
|
|
404
|
-
type,
|
|
405
|
-
contentType: entry.contentType,
|
|
406
|
-
locale: entry.locale,
|
|
407
|
-
entry: {
|
|
408
|
-
id: entry.id,
|
|
409
|
-
__type: entry.contentType,
|
|
410
|
-
__pivot: { field: "entry" }
|
|
411
|
-
},
|
|
412
|
-
release: releaseId
|
|
413
|
-
},
|
|
414
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
415
|
-
});
|
|
416
|
-
},
|
|
417
|
-
async findActions(releaseId, query) {
|
|
418
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
419
|
-
fields: ["id"]
|
|
420
|
-
});
|
|
421
|
-
if (!release2) {
|
|
422
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
423
|
-
}
|
|
424
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
425
|
-
...query,
|
|
426
|
-
populate: {
|
|
427
|
-
entry: {
|
|
428
|
-
populate: "*"
|
|
646
|
+
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
647
|
+
/*
|
|
648
|
+
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
649
|
+
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
650
|
+
*/
|
|
651
|
+
// @ts-expect-error see above
|
|
652
|
+
data: releaseWithCreatorFields
|
|
653
|
+
});
|
|
654
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
655
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
656
|
+
if (releaseData.scheduledAt) {
|
|
657
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
658
|
+
} else if (release2.scheduledAt) {
|
|
659
|
+
schedulingService.cancel(id);
|
|
429
660
|
}
|
|
430
|
-
},
|
|
431
|
-
filters: {
|
|
432
|
-
release: releaseId
|
|
433
661
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
662
|
+
this.updateReleaseStatus(id);
|
|
663
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
664
|
+
return updatedRelease;
|
|
665
|
+
},
|
|
666
|
+
async createAction(releaseId, action) {
|
|
667
|
+
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
668
|
+
strapi: strapi2
|
|
669
|
+
});
|
|
670
|
+
await Promise.all([
|
|
671
|
+
validateEntryContentType(action.entry.contentType),
|
|
672
|
+
validateUniqueEntry(releaseId, action)
|
|
673
|
+
]);
|
|
674
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
675
|
+
if (!release2) {
|
|
676
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
443
677
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
},
|
|
465
|
-
async getLocalesDataForActions() {
|
|
466
|
-
if (!strapi2.plugin("i18n")) {
|
|
467
|
-
return {};
|
|
468
|
-
}
|
|
469
|
-
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
470
|
-
return allLocales.reduce((acc, locale) => {
|
|
471
|
-
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
472
|
-
return acc;
|
|
473
|
-
}, {});
|
|
474
|
-
},
|
|
475
|
-
async getContentTypesDataForActions(contentTypesUids) {
|
|
476
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
477
|
-
const contentTypesData = {};
|
|
478
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
479
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
480
|
-
uid: contentTypeUid
|
|
678
|
+
if (release2.releasedAt) {
|
|
679
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
680
|
+
}
|
|
681
|
+
const { entry, type } = action;
|
|
682
|
+
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
683
|
+
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
684
|
+
const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
685
|
+
data: {
|
|
686
|
+
type,
|
|
687
|
+
contentType: entry.contentType,
|
|
688
|
+
locale: entry.locale,
|
|
689
|
+
isEntryValid,
|
|
690
|
+
entry: {
|
|
691
|
+
id: entry.id,
|
|
692
|
+
__type: entry.contentType,
|
|
693
|
+
__pivot: { field: "entry" }
|
|
694
|
+
},
|
|
695
|
+
release: releaseId
|
|
696
|
+
},
|
|
697
|
+
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
481
698
|
});
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (!acc.includes(action.contentType)) {
|
|
492
|
-
acc.push(action.contentType);
|
|
699
|
+
this.updateReleaseStatus(releaseId);
|
|
700
|
+
return releaseAction2;
|
|
701
|
+
},
|
|
702
|
+
async findActions(releaseId, query) {
|
|
703
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
704
|
+
fields: ["id"]
|
|
705
|
+
});
|
|
706
|
+
if (!release2) {
|
|
707
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
493
708
|
}
|
|
494
|
-
return
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
709
|
+
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
710
|
+
...query,
|
|
711
|
+
populate: {
|
|
712
|
+
entry: {
|
|
713
|
+
populate: "*"
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
filters: {
|
|
717
|
+
release: releaseId
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
},
|
|
721
|
+
async countActions(query) {
|
|
722
|
+
return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
|
|
723
|
+
},
|
|
724
|
+
async groupActions(actions, groupBy) {
|
|
725
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
726
|
+
if (!acc.includes(action.contentType)) {
|
|
727
|
+
acc.push(action.contentType);
|
|
728
|
+
}
|
|
499
729
|
return acc;
|
|
500
|
-
},
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
730
|
+
}, []);
|
|
731
|
+
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
|
|
732
|
+
contentTypeUids
|
|
733
|
+
);
|
|
734
|
+
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
735
|
+
const formattedData = actions.map((action) => {
|
|
736
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
737
|
+
return {
|
|
738
|
+
...action,
|
|
739
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
740
|
+
contentType: {
|
|
741
|
+
displayName,
|
|
742
|
+
mainFieldValue: action.entry[mainField],
|
|
743
|
+
uid: action.contentType
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
});
|
|
747
|
+
const groupName = getGroupName(groupBy);
|
|
748
|
+
return ___default.default.groupBy(groupName)(formattedData);
|
|
749
|
+
},
|
|
750
|
+
async getLocalesDataForActions() {
|
|
751
|
+
if (!strapi2.plugin("i18n")) {
|
|
752
|
+
return {};
|
|
753
|
+
}
|
|
754
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
755
|
+
return allLocales.reduce((acc, locale) => {
|
|
756
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
511
757
|
return acc;
|
|
512
|
-
},
|
|
513
|
-
|
|
514
|
-
)
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
758
|
+
}, {});
|
|
759
|
+
},
|
|
760
|
+
async getContentTypesDataForActions(contentTypesUids) {
|
|
761
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
762
|
+
const contentTypesData = {};
|
|
763
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
764
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
765
|
+
uid: contentTypeUid
|
|
766
|
+
});
|
|
767
|
+
contentTypesData[contentTypeUid] = {
|
|
768
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
769
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
770
|
+
};
|
|
523
771
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
}
|
|
531
|
-
await strapi2.db.transaction(async () => {
|
|
532
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
533
|
-
where: {
|
|
534
|
-
id: {
|
|
535
|
-
$in: release2.actions.map((action) => action.id)
|
|
536
|
-
}
|
|
772
|
+
return contentTypesData;
|
|
773
|
+
},
|
|
774
|
+
getContentTypeModelsFromActions(actions) {
|
|
775
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
776
|
+
if (!acc.includes(action.contentType)) {
|
|
777
|
+
acc.push(action.contentType);
|
|
537
778
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
779
|
+
return acc;
|
|
780
|
+
}, []);
|
|
781
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
782
|
+
(acc, contentTypeUid) => {
|
|
783
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
784
|
+
return acc;
|
|
785
|
+
},
|
|
786
|
+
{}
|
|
787
|
+
);
|
|
788
|
+
return contentTypeModelsMap;
|
|
789
|
+
},
|
|
790
|
+
async getAllComponents() {
|
|
791
|
+
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
792
|
+
const components = await contentManagerComponentsService.findAllComponents();
|
|
793
|
+
const componentsMap = components.reduce(
|
|
794
|
+
(acc, component) => {
|
|
795
|
+
acc[component.uid] = component;
|
|
796
|
+
return acc;
|
|
797
|
+
},
|
|
798
|
+
{}
|
|
799
|
+
);
|
|
800
|
+
return componentsMap;
|
|
801
|
+
},
|
|
802
|
+
async delete(releaseId) {
|
|
803
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
548
804
|
populate: {
|
|
549
805
|
actions: {
|
|
550
|
-
|
|
551
|
-
entry: {
|
|
552
|
-
fields: ["id"]
|
|
553
|
-
}
|
|
554
|
-
}
|
|
806
|
+
fields: ["id"]
|
|
555
807
|
}
|
|
556
808
|
}
|
|
809
|
+
});
|
|
810
|
+
if (!release2) {
|
|
811
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
557
812
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
561
|
-
}
|
|
562
|
-
if (releaseWithPopulatedActionEntries.releasedAt) {
|
|
563
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
564
|
-
}
|
|
565
|
-
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
566
|
-
throw new utils.errors.ValidationError("No entries to publish");
|
|
567
|
-
}
|
|
568
|
-
const actions = {};
|
|
569
|
-
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
570
|
-
const contentTypeUid = action.contentType;
|
|
571
|
-
if (!actions[contentTypeUid]) {
|
|
572
|
-
actions[contentTypeUid] = {
|
|
573
|
-
entriestoPublishIds: [],
|
|
574
|
-
entriesToUnpublishIds: []
|
|
575
|
-
};
|
|
813
|
+
if (release2.releasedAt) {
|
|
814
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
576
815
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
816
|
+
await strapi2.db.transaction(async () => {
|
|
817
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
818
|
+
where: {
|
|
819
|
+
id: {
|
|
820
|
+
$in: release2.actions.map((action) => action.id)
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
|
825
|
+
});
|
|
826
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
|
|
827
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
828
|
+
await schedulingService.cancel(release2.id);
|
|
581
829
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
contentTypeUid,
|
|
830
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
831
|
+
return release2;
|
|
832
|
+
},
|
|
833
|
+
async publish(releaseId) {
|
|
834
|
+
try {
|
|
835
|
+
const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
|
|
836
|
+
RELEASE_MODEL_UID,
|
|
837
|
+
releaseId,
|
|
591
838
|
{
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
839
|
+
populate: {
|
|
840
|
+
actions: {
|
|
841
|
+
populate: {
|
|
842
|
+
entry: {
|
|
843
|
+
fields: ["id"]
|
|
844
|
+
}
|
|
845
|
+
}
|
|
595
846
|
}
|
|
596
|
-
}
|
|
597
|
-
populate
|
|
847
|
+
}
|
|
598
848
|
}
|
|
599
849
|
);
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
850
|
+
if (!releaseWithPopulatedActionEntries) {
|
|
851
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
852
|
+
}
|
|
853
|
+
if (releaseWithPopulatedActionEntries.releasedAt) {
|
|
854
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
855
|
+
}
|
|
856
|
+
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
857
|
+
throw new utils.errors.ValidationError("No entries to publish");
|
|
858
|
+
}
|
|
859
|
+
const collectionTypeActions = {};
|
|
860
|
+
const singleTypeActions = [];
|
|
861
|
+
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
862
|
+
const contentTypeUid = action.contentType;
|
|
863
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
864
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
865
|
+
collectionTypeActions[contentTypeUid] = {
|
|
866
|
+
entriestoPublishIds: [],
|
|
867
|
+
entriesToUnpublishIds: []
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
if (action.type === "publish") {
|
|
871
|
+
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
872
|
+
} else {
|
|
873
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
874
|
+
}
|
|
875
|
+
} else {
|
|
876
|
+
singleTypeActions.push({
|
|
877
|
+
uid: contentTypeUid,
|
|
878
|
+
action: action.type,
|
|
879
|
+
id: action.entry.id
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
884
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
885
|
+
await strapi2.db.transaction(async () => {
|
|
886
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
887
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
888
|
+
const entry = await strapi2.entityService.findOne(uid, id, { populate });
|
|
889
|
+
try {
|
|
890
|
+
if (action === "publish") {
|
|
891
|
+
await entityManagerService.publish(entry, uid);
|
|
892
|
+
} else {
|
|
893
|
+
await entityManagerService.unpublish(entry, uid);
|
|
606
894
|
}
|
|
607
|
-
}
|
|
608
|
-
|
|
895
|
+
} catch (error) {
|
|
896
|
+
if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
|
|
897
|
+
} else {
|
|
898
|
+
throw error;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
609
901
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
902
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
903
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
904
|
+
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
905
|
+
const entriesToPublish = await strapi2.entityService.findMany(
|
|
906
|
+
contentTypeUid,
|
|
907
|
+
{
|
|
908
|
+
filters: {
|
|
909
|
+
id: {
|
|
910
|
+
$in: entriestoPublishIds
|
|
911
|
+
}
|
|
912
|
+
},
|
|
913
|
+
populate
|
|
914
|
+
}
|
|
915
|
+
);
|
|
916
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(
|
|
917
|
+
contentTypeUid,
|
|
918
|
+
{
|
|
919
|
+
filters: {
|
|
920
|
+
id: {
|
|
921
|
+
$in: entriesToUnpublishIds
|
|
922
|
+
}
|
|
923
|
+
},
|
|
924
|
+
populate
|
|
925
|
+
}
|
|
926
|
+
);
|
|
927
|
+
if (entriesToPublish.length > 0) {
|
|
928
|
+
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
929
|
+
}
|
|
930
|
+
if (entriesToUnpublish.length > 0) {
|
|
931
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
|
|
936
|
+
data: {
|
|
937
|
+
/*
|
|
938
|
+
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
|
|
939
|
+
*/
|
|
940
|
+
// @ts-expect-error see above
|
|
941
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
942
|
+
},
|
|
943
|
+
populate: {
|
|
944
|
+
actions: {
|
|
945
|
+
// @ts-expect-error is not expecting count but it is working
|
|
946
|
+
count: true
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
951
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
952
|
+
isPublished: true,
|
|
953
|
+
release: release2
|
|
954
|
+
});
|
|
613
955
|
}
|
|
614
|
-
|
|
615
|
-
|
|
956
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
957
|
+
return release2;
|
|
958
|
+
} catch (error) {
|
|
959
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
960
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
961
|
+
isPublished: false,
|
|
962
|
+
error
|
|
963
|
+
});
|
|
616
964
|
}
|
|
965
|
+
strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
966
|
+
where: { id: releaseId },
|
|
967
|
+
data: {
|
|
968
|
+
status: "failed"
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
throw error;
|
|
617
972
|
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
973
|
+
},
|
|
974
|
+
async updateAction(actionId, releaseId, update) {
|
|
975
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
976
|
+
where: {
|
|
977
|
+
id: actionId,
|
|
978
|
+
release: {
|
|
979
|
+
id: releaseId,
|
|
980
|
+
releasedAt: {
|
|
981
|
+
$null: true
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
},
|
|
985
|
+
data: update
|
|
986
|
+
});
|
|
987
|
+
if (!updatedAction) {
|
|
988
|
+
throw new utils.errors.NotFoundError(
|
|
989
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
990
|
+
);
|
|
626
991
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
992
|
+
return updatedAction;
|
|
993
|
+
},
|
|
994
|
+
async deleteAction(actionId, releaseId) {
|
|
995
|
+
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
996
|
+
where: {
|
|
997
|
+
id: actionId,
|
|
998
|
+
release: {
|
|
999
|
+
id: releaseId,
|
|
1000
|
+
releasedAt: {
|
|
1001
|
+
$null: true
|
|
1002
|
+
}
|
|
638
1003
|
}
|
|
639
1004
|
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
);
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
1005
|
+
});
|
|
1006
|
+
if (!deletedAction) {
|
|
1007
|
+
throw new utils.errors.NotFoundError(
|
|
1008
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
this.updateReleaseStatus(releaseId);
|
|
1012
|
+
return deletedAction;
|
|
1013
|
+
},
|
|
1014
|
+
async updateReleaseStatus(releaseId) {
|
|
1015
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
1016
|
+
this.countActions({
|
|
1017
|
+
filters: {
|
|
1018
|
+
release: releaseId
|
|
1019
|
+
}
|
|
1020
|
+
}),
|
|
1021
|
+
this.countActions({
|
|
1022
|
+
filters: {
|
|
1023
|
+
release: releaseId,
|
|
1024
|
+
isEntryValid: false
|
|
658
1025
|
}
|
|
1026
|
+
})
|
|
1027
|
+
]);
|
|
1028
|
+
if (totalActions > 0) {
|
|
1029
|
+
if (invalidActions > 0) {
|
|
1030
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1031
|
+
where: {
|
|
1032
|
+
id: releaseId
|
|
1033
|
+
},
|
|
1034
|
+
data: {
|
|
1035
|
+
status: "blocked"
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
659
1038
|
}
|
|
1039
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1040
|
+
where: {
|
|
1041
|
+
id: releaseId
|
|
1042
|
+
},
|
|
1043
|
+
data: {
|
|
1044
|
+
status: "ready"
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
660
1047
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
1048
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1049
|
+
where: {
|
|
1050
|
+
id: releaseId
|
|
1051
|
+
},
|
|
1052
|
+
data: {
|
|
1053
|
+
status: "empty"
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
666
1056
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
});
|
|
1057
|
+
};
|
|
1058
|
+
};
|
|
670
1059
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
671
1060
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
672
1061
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
@@ -711,27 +1100,103 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
711
1100
|
throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
|
|
712
1101
|
}
|
|
713
1102
|
},
|
|
714
|
-
async validateUniqueNameForPendingRelease(name) {
|
|
1103
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
715
1104
|
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
716
1105
|
filters: {
|
|
717
1106
|
releasedAt: {
|
|
718
1107
|
$null: true
|
|
719
1108
|
},
|
|
720
|
-
name
|
|
1109
|
+
name,
|
|
1110
|
+
...id && { id: { $ne: id } }
|
|
721
1111
|
}
|
|
722
1112
|
});
|
|
723
1113
|
const isNameUnique = pendingReleases.length === 0;
|
|
724
1114
|
if (!isNameUnique) {
|
|
725
1115
|
throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
|
|
726
1116
|
}
|
|
1117
|
+
},
|
|
1118
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
1119
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
1120
|
+
throw new utils.errors.ValidationError("Scheduled at must be later than now");
|
|
1121
|
+
}
|
|
727
1122
|
}
|
|
728
1123
|
});
|
|
1124
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
1125
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
1126
|
+
return {
|
|
1127
|
+
async set(releaseId, scheduleDate) {
|
|
1128
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
1129
|
+
if (!release2) {
|
|
1130
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1131
|
+
}
|
|
1132
|
+
const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
|
|
1133
|
+
try {
|
|
1134
|
+
await getService("release").publish(releaseId);
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
}
|
|
1137
|
+
this.cancel(releaseId);
|
|
1138
|
+
});
|
|
1139
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1140
|
+
this.cancel(releaseId);
|
|
1141
|
+
}
|
|
1142
|
+
scheduledJobs.set(releaseId, job);
|
|
1143
|
+
return scheduledJobs;
|
|
1144
|
+
},
|
|
1145
|
+
cancel(releaseId) {
|
|
1146
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1147
|
+
scheduledJobs.get(releaseId).cancel();
|
|
1148
|
+
scheduledJobs.delete(releaseId);
|
|
1149
|
+
}
|
|
1150
|
+
return scheduledJobs;
|
|
1151
|
+
},
|
|
1152
|
+
getAll() {
|
|
1153
|
+
return scheduledJobs;
|
|
1154
|
+
},
|
|
1155
|
+
/**
|
|
1156
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
1157
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
1158
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
1159
|
+
*/
|
|
1160
|
+
async syncFromDatabase() {
|
|
1161
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1162
|
+
where: {
|
|
1163
|
+
scheduledAt: {
|
|
1164
|
+
$gte: /* @__PURE__ */ new Date()
|
|
1165
|
+
},
|
|
1166
|
+
releasedAt: null
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
for (const release2 of releases) {
|
|
1170
|
+
this.set(release2.id, release2.scheduledAt);
|
|
1171
|
+
}
|
|
1172
|
+
return scheduledJobs;
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
};
|
|
729
1176
|
const services = {
|
|
730
1177
|
release: createReleaseService,
|
|
731
|
-
"release-validation": createReleaseValidationService
|
|
1178
|
+
"release-validation": createReleaseValidationService,
|
|
1179
|
+
...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
|
|
732
1180
|
};
|
|
733
1181
|
const RELEASE_SCHEMA = yup__namespace.object().shape({
|
|
734
|
-
name: yup__namespace.string().trim().required()
|
|
1182
|
+
name: yup__namespace.string().trim().required(),
|
|
1183
|
+
scheduledAt: yup__namespace.string().nullable(),
|
|
1184
|
+
isScheduled: yup__namespace.boolean().optional(),
|
|
1185
|
+
time: yup__namespace.string().when("isScheduled", {
|
|
1186
|
+
is: true,
|
|
1187
|
+
then: yup__namespace.string().trim().required(),
|
|
1188
|
+
otherwise: yup__namespace.string().nullable()
|
|
1189
|
+
}),
|
|
1190
|
+
timezone: yup__namespace.string().when("isScheduled", {
|
|
1191
|
+
is: true,
|
|
1192
|
+
then: yup__namespace.string().required().nullable(),
|
|
1193
|
+
otherwise: yup__namespace.string().nullable()
|
|
1194
|
+
}),
|
|
1195
|
+
date: yup__namespace.string().when("isScheduled", {
|
|
1196
|
+
is: true,
|
|
1197
|
+
then: yup__namespace.string().required().nullable(),
|
|
1198
|
+
otherwise: yup__namespace.string().nullable()
|
|
1199
|
+
})
|
|
735
1200
|
}).required().noUnknown();
|
|
736
1201
|
const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
|
|
737
1202
|
const releaseController = {
|
|
@@ -764,26 +1229,30 @@ const releaseController = {
|
|
|
764
1229
|
}
|
|
765
1230
|
};
|
|
766
1231
|
});
|
|
767
|
-
|
|
1232
|
+
const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
|
|
1233
|
+
where: {
|
|
1234
|
+
releasedAt: null
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
768
1238
|
}
|
|
769
1239
|
},
|
|
770
1240
|
async findOne(ctx) {
|
|
771
1241
|
const id = ctx.params.id;
|
|
772
1242
|
const releaseService = getService("release", { strapi });
|
|
773
1243
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
});
|
|
778
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
1244
|
+
if (!release2) {
|
|
1245
|
+
throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
|
|
1246
|
+
}
|
|
779
1247
|
const count = await releaseService.countActions({
|
|
780
1248
|
filters: {
|
|
781
1249
|
release: id
|
|
782
1250
|
}
|
|
783
1251
|
});
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
1252
|
+
const sanitizedRelease = {
|
|
1253
|
+
...release2,
|
|
1254
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
1255
|
+
};
|
|
787
1256
|
const data = {
|
|
788
1257
|
...sanitizedRelease,
|
|
789
1258
|
actions: {
|
|
@@ -836,8 +1305,27 @@ const releaseController = {
|
|
|
836
1305
|
const id = ctx.params.id;
|
|
837
1306
|
const releaseService = getService("release", { strapi });
|
|
838
1307
|
const release2 = await releaseService.publish(id, { user });
|
|
1308
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1309
|
+
releaseService.countActions({
|
|
1310
|
+
filters: {
|
|
1311
|
+
release: id,
|
|
1312
|
+
type: "publish"
|
|
1313
|
+
}
|
|
1314
|
+
}),
|
|
1315
|
+
releaseService.countActions({
|
|
1316
|
+
filters: {
|
|
1317
|
+
release: id,
|
|
1318
|
+
type: "unpublish"
|
|
1319
|
+
}
|
|
1320
|
+
})
|
|
1321
|
+
]);
|
|
839
1322
|
ctx.body = {
|
|
840
|
-
data: release2
|
|
1323
|
+
data: release2,
|
|
1324
|
+
meta: {
|
|
1325
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1326
|
+
totalPublishedEntries: countPublishActions,
|
|
1327
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1328
|
+
}
|
|
841
1329
|
};
|
|
842
1330
|
}
|
|
843
1331
|
};
|
|
@@ -1109,6 +1597,7 @@ const getPlugin = () => {
|
|
|
1109
1597
|
return {
|
|
1110
1598
|
register,
|
|
1111
1599
|
bootstrap,
|
|
1600
|
+
destroy,
|
|
1112
1601
|
contentTypes,
|
|
1113
1602
|
services,
|
|
1114
1603
|
controllers,
|