@strapi/content-releases 0.0.0-next.1168c576ca50587b1a4377082ee09d2375410204 → 0.0.0-next.16afe21665d27a27f5c86939ba362066eabf1982
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/LICENSE +17 -1
- package/dist/_chunks/{App-_20W9dYa.js → App-dLXY5ei3.js} +784 -468
- package/dist/_chunks/App-dLXY5ei3.js.map +1 -0
- package/dist/_chunks/App-jrh58sXY.mjs +1330 -0
- package/dist/_chunks/App-jrh58sXY.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs +51 -0
- package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js +51 -0
- package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js.map +1 -0
- package/dist/_chunks/{en-gYDqKYFd.js → en-HrREghh3.js} +30 -7
- package/dist/_chunks/en-HrREghh3.js.map +1 -0
- package/dist/_chunks/{en-MyLPoISH.mjs → en-ltT1TlKQ.mjs} +30 -7
- package/dist/_chunks/en-ltT1TlKQ.mjs.map +1 -0
- package/dist/_chunks/{index-KJa1Rb5F.js → index-CVO0Rqdm.js} +469 -41
- package/dist/_chunks/index-CVO0Rqdm.js.map +1 -0
- package/dist/_chunks/{index-c4zRX_sg.mjs → index-PiOGBETy.mjs} +486 -58
- package/dist/_chunks/index-PiOGBETy.mjs.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +2 -2
- package/dist/server/index.js +1066 -398
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1065 -398
- package/dist/server/index.mjs.map +1 -1
- package/package.json +16 -13
- package/dist/_chunks/App-L1jSxCiL.mjs +0 -1015
- 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,202 @@ 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 && action.release) {
|
|
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
|
+
}
|
|
234
|
+
async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
235
|
+
if (!oldContentTypes) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
239
|
+
if (!i18nPlugin) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
for (const uid in contentTypes2) {
|
|
243
|
+
if (!oldContentTypes[uid]) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const oldContentType = oldContentTypes[uid];
|
|
247
|
+
const contentType = contentTypes2[uid];
|
|
248
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
249
|
+
if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
|
|
250
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
251
|
+
locale: null
|
|
252
|
+
}).where({ contentType: uid }).execute();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
257
|
+
if (!oldContentTypes) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
261
|
+
if (!i18nPlugin) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
for (const uid in contentTypes2) {
|
|
265
|
+
if (!oldContentTypes[uid]) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const oldContentType = oldContentTypes[uid];
|
|
269
|
+
const contentType = contentTypes2[uid];
|
|
270
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
271
|
+
const { getDefaultLocale } = i18nPlugin.service("locales");
|
|
272
|
+
if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
|
|
273
|
+
const defaultLocale = await getDefaultLocale();
|
|
274
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
275
|
+
locale: defaultLocale
|
|
276
|
+
}).where({ contentType: uid }).execute();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
101
280
|
const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
|
|
102
281
|
const register = async ({ strapi: strapi2 }) => {
|
|
103
282
|
if (features$2.isEnabled("cms-content-releases")) {
|
|
104
283
|
await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
|
105
|
-
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
|
|
106
|
-
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
|
|
284
|
+
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish).register(disableContentTypeLocalized);
|
|
285
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
286
|
+
}
|
|
287
|
+
if (strapi2.plugin("graphql")) {
|
|
288
|
+
const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
|
|
289
|
+
graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
|
|
290
|
+
graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
|
|
107
291
|
}
|
|
108
292
|
};
|
|
109
293
|
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
110
294
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
111
295
|
if (features$1.isEnabled("cms-content-releases")) {
|
|
296
|
+
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
297
|
+
(uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
|
|
298
|
+
);
|
|
112
299
|
strapi2.db.lifecycles.subscribe({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
300
|
+
models: contentTypesWithDraftAndPublish,
|
|
301
|
+
async afterDelete(event) {
|
|
302
|
+
try {
|
|
303
|
+
const { model, result } = event;
|
|
304
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
305
|
+
const { id } = result;
|
|
306
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
307
|
+
where: {
|
|
308
|
+
actions: {
|
|
309
|
+
target_type: model.uid,
|
|
310
|
+
target_id: id
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
315
|
+
where: {
|
|
316
|
+
target_type: model.uid,
|
|
317
|
+
target_id: id
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
for (const release2 of releases) {
|
|
321
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
121
322
|
}
|
|
122
|
-
}
|
|
323
|
+
}
|
|
324
|
+
} catch (error) {
|
|
325
|
+
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
123
326
|
}
|
|
124
327
|
},
|
|
125
328
|
/**
|
|
@@ -139,20 +342,94 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
139
342
|
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
140
343
|
*/
|
|
141
344
|
async afterDeleteMany(event) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
345
|
+
try {
|
|
346
|
+
const { model, state } = event;
|
|
347
|
+
const entriesToDelete = state.entriesToDelete;
|
|
348
|
+
if (entriesToDelete) {
|
|
349
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
350
|
+
where: {
|
|
351
|
+
actions: {
|
|
352
|
+
target_type: model.uid,
|
|
353
|
+
target_id: {
|
|
354
|
+
$in: entriesToDelete.map(
|
|
355
|
+
(entry) => entry.id
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
362
|
+
where: {
|
|
363
|
+
target_type: model.uid,
|
|
364
|
+
target_id: {
|
|
365
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
366
|
+
}
|
|
150
367
|
}
|
|
368
|
+
});
|
|
369
|
+
for (const release2 of releases) {
|
|
370
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
151
371
|
}
|
|
372
|
+
}
|
|
373
|
+
} catch (error) {
|
|
374
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
375
|
+
error
|
|
152
376
|
});
|
|
153
377
|
}
|
|
378
|
+
},
|
|
379
|
+
async afterUpdate(event) {
|
|
380
|
+
try {
|
|
381
|
+
const { model, result } = event;
|
|
382
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
383
|
+
const isEntryValid = await getEntryValidStatus(
|
|
384
|
+
model.uid,
|
|
385
|
+
result,
|
|
386
|
+
{
|
|
387
|
+
strapi: strapi2
|
|
388
|
+
}
|
|
389
|
+
);
|
|
390
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
391
|
+
where: {
|
|
392
|
+
target_type: model.uid,
|
|
393
|
+
target_id: result.id
|
|
394
|
+
},
|
|
395
|
+
data: {
|
|
396
|
+
isEntryValid
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
400
|
+
where: {
|
|
401
|
+
actions: {
|
|
402
|
+
target_type: model.uid,
|
|
403
|
+
target_id: result.id
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
for (const release2 of releases) {
|
|
408
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
} catch (error) {
|
|
412
|
+
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
413
|
+
}
|
|
154
414
|
}
|
|
155
415
|
});
|
|
416
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
417
|
+
strapi2.log.error(
|
|
418
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
419
|
+
);
|
|
420
|
+
throw err;
|
|
421
|
+
});
|
|
422
|
+
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
423
|
+
strapi2.webhookStore.addAllowedEvent(key, value);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
const destroy = async ({ strapi: strapi2 }) => {
|
|
428
|
+
const scheduledJobs = getService("scheduling", {
|
|
429
|
+
strapi: strapi2
|
|
430
|
+
}).getAll();
|
|
431
|
+
for (const [, job] of scheduledJobs) {
|
|
432
|
+
job.cancel();
|
|
156
433
|
}
|
|
157
434
|
};
|
|
158
435
|
const schema$1 = {
|
|
@@ -181,6 +458,17 @@ const schema$1 = {
|
|
|
181
458
|
releasedAt: {
|
|
182
459
|
type: "datetime"
|
|
183
460
|
},
|
|
461
|
+
scheduledAt: {
|
|
462
|
+
type: "datetime"
|
|
463
|
+
},
|
|
464
|
+
timezone: {
|
|
465
|
+
type: "string"
|
|
466
|
+
},
|
|
467
|
+
status: {
|
|
468
|
+
type: "enumeration",
|
|
469
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
470
|
+
required: true
|
|
471
|
+
},
|
|
184
472
|
actions: {
|
|
185
473
|
type: "relation",
|
|
186
474
|
relation: "oneToMany",
|
|
@@ -233,6 +521,9 @@ const schema = {
|
|
|
233
521
|
relation: "manyToOne",
|
|
234
522
|
target: RELEASE_MODEL_UID,
|
|
235
523
|
inversedBy: "actions"
|
|
524
|
+
},
|
|
525
|
+
isEntryValid: {
|
|
526
|
+
type: "boolean"
|
|
236
527
|
}
|
|
237
528
|
}
|
|
238
529
|
};
|
|
@@ -243,9 +534,6 @@ const contentTypes = {
|
|
|
243
534
|
release: release$1,
|
|
244
535
|
"release-action": releaseAction$1
|
|
245
536
|
};
|
|
246
|
-
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
247
|
-
return strapi2.plugin("content-releases").service(name);
|
|
248
|
-
};
|
|
249
537
|
const getGroupName = (queryValue) => {
|
|
250
538
|
switch (queryValue) {
|
|
251
539
|
case "contentType":
|
|
@@ -258,415 +546,596 @@ const getGroupName = (queryValue) => {
|
|
|
258
546
|
return "contentType.displayName";
|
|
259
547
|
}
|
|
260
548
|
};
|
|
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
|
|
549
|
+
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
550
|
+
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
551
|
+
strapi2.eventHub.emit(event, {
|
|
552
|
+
isPublished,
|
|
553
|
+
error,
|
|
554
|
+
release: release2
|
|
274
555
|
});
|
|
275
|
-
}
|
|
276
|
-
async
|
|
277
|
-
const
|
|
278
|
-
|
|
556
|
+
};
|
|
557
|
+
const publishSingleTypeAction = async (uid, actionType, entryId) => {
|
|
558
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
559
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
560
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
561
|
+
const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
|
|
562
|
+
try {
|
|
563
|
+
if (actionType === "publish") {
|
|
564
|
+
await entityManagerService.publish(entry, uid);
|
|
565
|
+
} else {
|
|
566
|
+
await entityManagerService.unpublish(entry, uid);
|
|
567
|
+
}
|
|
568
|
+
} catch (error) {
|
|
569
|
+
if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
570
|
+
;
|
|
571
|
+
else {
|
|
572
|
+
throw error;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
|
|
577
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
578
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
579
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
580
|
+
const entriesToPublish = await strapi2.entityService.findMany(uid, {
|
|
581
|
+
filters: {
|
|
582
|
+
id: {
|
|
583
|
+
$in: entriesToPublishIds
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
populate
|
|
279
587
|
});
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
...query,
|
|
285
|
-
populate: {
|
|
286
|
-
actions: {
|
|
287
|
-
// @ts-expect-error Ignore missing properties
|
|
288
|
-
count: true
|
|
588
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
|
|
589
|
+
filters: {
|
|
590
|
+
id: {
|
|
591
|
+
$in: entriestoUnpublishIds
|
|
289
592
|
}
|
|
290
|
-
}
|
|
593
|
+
},
|
|
594
|
+
populate
|
|
291
595
|
});
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
596
|
+
if (entriesToPublish.length > 0) {
|
|
597
|
+
await entityManagerService.publishMany(entriesToPublish, uid);
|
|
598
|
+
}
|
|
599
|
+
if (entriesToUnpublish.length > 0) {
|
|
600
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, uid);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
const getFormattedActions = async (releaseId) => {
|
|
604
|
+
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
295
605
|
where: {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
target_id: entryId
|
|
299
|
-
},
|
|
300
|
-
releasedAt: {
|
|
301
|
-
$null: true
|
|
606
|
+
release: {
|
|
607
|
+
id: releaseId
|
|
302
608
|
}
|
|
303
609
|
},
|
|
304
610
|
populate: {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
where: {
|
|
308
|
-
target_type: contentTypeUid,
|
|
309
|
-
target_id: entryId
|
|
310
|
-
}
|
|
611
|
+
entry: {
|
|
612
|
+
fields: ["id"]
|
|
311
613
|
}
|
|
312
614
|
}
|
|
313
615
|
});
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
616
|
+
if (actions.length === 0) {
|
|
617
|
+
throw new utils.errors.ValidationError("No entries to publish");
|
|
618
|
+
}
|
|
619
|
+
const collectionTypeActions = {};
|
|
620
|
+
const singleTypeActions = [];
|
|
621
|
+
for (const action of actions) {
|
|
622
|
+
const contentTypeUid = action.contentType;
|
|
623
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
624
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
625
|
+
collectionTypeActions[contentTypeUid] = {
|
|
626
|
+
entriesToPublishIds: [],
|
|
627
|
+
entriesToUnpublishIds: []
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
if (action.type === "publish") {
|
|
631
|
+
collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
|
|
632
|
+
} else {
|
|
633
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
634
|
+
}
|
|
635
|
+
} else {
|
|
636
|
+
singleTypeActions.push({
|
|
637
|
+
uid: contentTypeUid,
|
|
638
|
+
action: action.type,
|
|
639
|
+
id: action.entry.id
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return { collectionTypeActions, singleTypeActions };
|
|
644
|
+
};
|
|
645
|
+
return {
|
|
646
|
+
async create(releaseData, { user }) {
|
|
647
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
|
|
648
|
+
const {
|
|
649
|
+
validatePendingReleasesLimit,
|
|
650
|
+
validateUniqueNameForPendingRelease,
|
|
651
|
+
validateScheduledAtIsLaterThanNow
|
|
652
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
653
|
+
await Promise.all([
|
|
654
|
+
validatePendingReleasesLimit(),
|
|
655
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
656
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
657
|
+
]);
|
|
658
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
659
|
+
data: {
|
|
660
|
+
...releaseWithCreatorFields,
|
|
661
|
+
status: "empty"
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
if (releaseWithCreatorFields.scheduledAt) {
|
|
665
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
666
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
322
667
|
}
|
|
668
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
323
669
|
return release2;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
670
|
+
},
|
|
671
|
+
async findOne(id, query = {}) {
|
|
672
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
673
|
+
...query
|
|
674
|
+
});
|
|
675
|
+
return release2;
|
|
676
|
+
},
|
|
677
|
+
findPage(query) {
|
|
678
|
+
return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
|
|
679
|
+
...query,
|
|
680
|
+
populate: {
|
|
681
|
+
actions: {
|
|
682
|
+
// @ts-expect-error Ignore missing properties
|
|
683
|
+
count: true
|
|
684
|
+
}
|
|
335
685
|
}
|
|
686
|
+
});
|
|
687
|
+
},
|
|
688
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entriesIds) {
|
|
689
|
+
let entries = entriesIds;
|
|
690
|
+
if (!Array.isArray(entriesIds)) {
|
|
691
|
+
entries = [entriesIds];
|
|
336
692
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
$notIn: releasesRelated.map((release2) => release2.id)
|
|
693
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
694
|
+
where: {
|
|
695
|
+
actions: {
|
|
696
|
+
target_type: contentTypeUid,
|
|
697
|
+
target_id: {
|
|
698
|
+
$in: entries
|
|
344
699
|
}
|
|
345
700
|
},
|
|
346
|
-
{
|
|
347
|
-
|
|
701
|
+
releasedAt: {
|
|
702
|
+
$null: true
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
populate: {
|
|
706
|
+
// Filter the action to get only the content type entry
|
|
707
|
+
actions: {
|
|
708
|
+
where: {
|
|
709
|
+
target_type: contentTypeUid,
|
|
710
|
+
target_id: {
|
|
711
|
+
$in: entries
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
populate: {
|
|
715
|
+
entry: {
|
|
716
|
+
select: ["id"]
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
return releases.map((release2) => {
|
|
723
|
+
if (release2.actions?.length) {
|
|
724
|
+
const actionsForEntry = release2.actions;
|
|
725
|
+
delete release2.actions;
|
|
726
|
+
return {
|
|
727
|
+
...release2,
|
|
728
|
+
actions: actionsForEntry
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
return release2;
|
|
732
|
+
});
|
|
733
|
+
},
|
|
734
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
735
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
736
|
+
where: {
|
|
737
|
+
releasedAt: {
|
|
738
|
+
$null: true
|
|
739
|
+
},
|
|
740
|
+
actions: {
|
|
741
|
+
target_type: contentTypeUid,
|
|
742
|
+
target_id: entryId
|
|
348
743
|
}
|
|
349
|
-
],
|
|
350
|
-
releasedAt: {
|
|
351
|
-
$null: true
|
|
352
744
|
}
|
|
745
|
+
});
|
|
746
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
747
|
+
where: {
|
|
748
|
+
$or: [
|
|
749
|
+
{
|
|
750
|
+
id: {
|
|
751
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
actions: null
|
|
756
|
+
}
|
|
757
|
+
],
|
|
758
|
+
releasedAt: {
|
|
759
|
+
$null: true
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
return releases.map((release2) => {
|
|
764
|
+
if (release2.actions?.length) {
|
|
765
|
+
const [actionForEntry] = release2.actions;
|
|
766
|
+
delete release2.actions;
|
|
767
|
+
return {
|
|
768
|
+
...release2,
|
|
769
|
+
action: actionForEntry
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
return release2;
|
|
773
|
+
});
|
|
774
|
+
},
|
|
775
|
+
async update(id, releaseData, { user }) {
|
|
776
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(
|
|
777
|
+
releaseData
|
|
778
|
+
);
|
|
779
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
780
|
+
"release-validation",
|
|
781
|
+
{ strapi: strapi2 }
|
|
782
|
+
);
|
|
783
|
+
await Promise.all([
|
|
784
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
785
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
786
|
+
]);
|
|
787
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
788
|
+
if (!release2) {
|
|
789
|
+
throw new utils.errors.NotFoundError(`No release found for id ${id}`);
|
|
353
790
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (release2.actions?.length) {
|
|
357
|
-
const [actionForEntry] = release2.actions;
|
|
358
|
-
delete release2.actions;
|
|
359
|
-
return {
|
|
360
|
-
...release2,
|
|
361
|
-
action: actionForEntry
|
|
362
|
-
};
|
|
791
|
+
if (release2.releasedAt) {
|
|
792
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
363
793
|
}
|
|
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: "*"
|
|
429
|
-
}
|
|
430
|
-
},
|
|
431
|
-
filters: {
|
|
432
|
-
release: releaseId
|
|
794
|
+
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
795
|
+
/*
|
|
796
|
+
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
797
|
+
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
798
|
+
*/
|
|
799
|
+
// @ts-expect-error see above
|
|
800
|
+
data: releaseWithCreatorFields
|
|
801
|
+
});
|
|
802
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
803
|
+
if (releaseData.scheduledAt) {
|
|
804
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
805
|
+
} else if (release2.scheduledAt) {
|
|
806
|
+
schedulingService.cancel(id);
|
|
433
807
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
808
|
+
this.updateReleaseStatus(id);
|
|
809
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
810
|
+
return updatedRelease;
|
|
811
|
+
},
|
|
812
|
+
async createAction(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
|
|
813
|
+
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
814
|
+
strapi: strapi2
|
|
815
|
+
});
|
|
816
|
+
await Promise.all([
|
|
817
|
+
validateEntryContentType(action.entry.contentType),
|
|
818
|
+
validateUniqueEntry(releaseId, action)
|
|
819
|
+
]);
|
|
820
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
821
|
+
if (!release2) {
|
|
822
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
823
|
+
}
|
|
824
|
+
if (release2.releasedAt) {
|
|
825
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
826
|
+
}
|
|
827
|
+
const { entry, type } = action;
|
|
828
|
+
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
829
|
+
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
830
|
+
const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
831
|
+
data: {
|
|
832
|
+
type,
|
|
833
|
+
contentType: entry.contentType,
|
|
834
|
+
locale: entry.locale,
|
|
835
|
+
isEntryValid,
|
|
836
|
+
entry: {
|
|
837
|
+
id: entry.id,
|
|
838
|
+
__type: entry.contentType,
|
|
839
|
+
__pivot: { field: "entry" }
|
|
840
|
+
},
|
|
841
|
+
release: releaseId
|
|
842
|
+
},
|
|
843
|
+
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
844
|
+
});
|
|
845
|
+
if (!disableUpdateReleaseStatus) {
|
|
846
|
+
this.updateReleaseStatus(releaseId);
|
|
443
847
|
}
|
|
444
|
-
return
|
|
445
|
-
},
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
450
|
-
const formattedData = actions.map((action) => {
|
|
451
|
-
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
452
|
-
return {
|
|
453
|
-
...action,
|
|
454
|
-
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
455
|
-
contentType: {
|
|
456
|
-
displayName,
|
|
457
|
-
mainFieldValue: action.entry[mainField],
|
|
458
|
-
uid: action.contentType
|
|
459
|
-
}
|
|
460
|
-
};
|
|
461
|
-
});
|
|
462
|
-
const groupName = getGroupName(groupBy);
|
|
463
|
-
return ___default.default.groupBy(groupName)(formattedData);
|
|
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
|
|
848
|
+
return releaseAction2;
|
|
849
|
+
},
|
|
850
|
+
async findActions(releaseId, query) {
|
|
851
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
852
|
+
fields: ["id"]
|
|
481
853
|
});
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
return contentTypesData;
|
|
488
|
-
},
|
|
489
|
-
getContentTypeModelsFromActions(actions) {
|
|
490
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
491
|
-
if (!acc.includes(action.contentType)) {
|
|
492
|
-
acc.push(action.contentType);
|
|
854
|
+
if (!release2) {
|
|
855
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
493
856
|
}
|
|
494
|
-
return
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
857
|
+
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
858
|
+
...query,
|
|
859
|
+
populate: {
|
|
860
|
+
entry: {
|
|
861
|
+
populate: "*"
|
|
862
|
+
}
|
|
863
|
+
},
|
|
864
|
+
filters: {
|
|
865
|
+
release: releaseId
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
},
|
|
869
|
+
async countActions(query) {
|
|
870
|
+
return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
|
|
871
|
+
},
|
|
872
|
+
async groupActions(actions, groupBy) {
|
|
873
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
874
|
+
if (!acc.includes(action.contentType)) {
|
|
875
|
+
acc.push(action.contentType);
|
|
876
|
+
}
|
|
499
877
|
return acc;
|
|
500
|
-
},
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
878
|
+
}, []);
|
|
879
|
+
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
|
|
880
|
+
contentTypeUids
|
|
881
|
+
);
|
|
882
|
+
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
883
|
+
const formattedData = actions.map((action) => {
|
|
884
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
885
|
+
return {
|
|
886
|
+
...action,
|
|
887
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
888
|
+
contentType: {
|
|
889
|
+
displayName,
|
|
890
|
+
mainFieldValue: action.entry[mainField],
|
|
891
|
+
uid: action.contentType
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
});
|
|
895
|
+
const groupName = getGroupName(groupBy);
|
|
896
|
+
return ___default.default.groupBy(groupName)(formattedData);
|
|
897
|
+
},
|
|
898
|
+
async getLocalesDataForActions() {
|
|
899
|
+
if (!strapi2.plugin("i18n")) {
|
|
900
|
+
return {};
|
|
901
|
+
}
|
|
902
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
903
|
+
return allLocales.reduce((acc, locale) => {
|
|
904
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
511
905
|
return acc;
|
|
512
|
-
},
|
|
513
|
-
|
|
514
|
-
)
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
906
|
+
}, {});
|
|
907
|
+
},
|
|
908
|
+
async getContentTypesDataForActions(contentTypesUids) {
|
|
909
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
910
|
+
const contentTypesData = {};
|
|
911
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
912
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
913
|
+
uid: contentTypeUid
|
|
914
|
+
});
|
|
915
|
+
contentTypesData[contentTypeUid] = {
|
|
916
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
917
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
918
|
+
};
|
|
523
919
|
}
|
|
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
|
-
}
|
|
920
|
+
return contentTypesData;
|
|
921
|
+
},
|
|
922
|
+
getContentTypeModelsFromActions(actions) {
|
|
923
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
924
|
+
if (!acc.includes(action.contentType)) {
|
|
925
|
+
acc.push(action.contentType);
|
|
537
926
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
927
|
+
return acc;
|
|
928
|
+
}, []);
|
|
929
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
930
|
+
(acc, contentTypeUid) => {
|
|
931
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
932
|
+
return acc;
|
|
933
|
+
},
|
|
934
|
+
{}
|
|
935
|
+
);
|
|
936
|
+
return contentTypeModelsMap;
|
|
937
|
+
},
|
|
938
|
+
async getAllComponents() {
|
|
939
|
+
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
940
|
+
const components = await contentManagerComponentsService.findAllComponents();
|
|
941
|
+
const componentsMap = components.reduce(
|
|
942
|
+
(acc, component) => {
|
|
943
|
+
acc[component.uid] = component;
|
|
944
|
+
return acc;
|
|
945
|
+
},
|
|
946
|
+
{}
|
|
947
|
+
);
|
|
948
|
+
return componentsMap;
|
|
949
|
+
},
|
|
950
|
+
async delete(releaseId) {
|
|
951
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
548
952
|
populate: {
|
|
549
953
|
actions: {
|
|
550
|
-
|
|
551
|
-
entry: {
|
|
552
|
-
fields: ["id"]
|
|
553
|
-
}
|
|
554
|
-
}
|
|
954
|
+
fields: ["id"]
|
|
555
955
|
}
|
|
556
956
|
}
|
|
957
|
+
});
|
|
958
|
+
if (!release2) {
|
|
959
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
557
960
|
}
|
|
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
|
-
};
|
|
961
|
+
if (release2.releasedAt) {
|
|
962
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
576
963
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
964
|
+
await strapi2.db.transaction(async () => {
|
|
965
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
966
|
+
where: {
|
|
967
|
+
id: {
|
|
968
|
+
$in: release2.actions.map((action) => action.id)
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
|
973
|
+
});
|
|
974
|
+
if (release2.scheduledAt) {
|
|
975
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
976
|
+
await schedulingService.cancel(release2.id);
|
|
581
977
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
978
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
979
|
+
return release2;
|
|
980
|
+
},
|
|
981
|
+
async publish(releaseId) {
|
|
982
|
+
const {
|
|
983
|
+
release: release2,
|
|
984
|
+
error
|
|
985
|
+
} = await strapi2.db.transaction(async ({ trx }) => {
|
|
986
|
+
const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
|
|
987
|
+
if (!lockedRelease) {
|
|
988
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
989
|
+
}
|
|
990
|
+
if (lockedRelease.releasedAt) {
|
|
991
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
992
|
+
}
|
|
993
|
+
if (lockedRelease.status === "failed") {
|
|
994
|
+
throw new utils.errors.ValidationError("Release failed to publish");
|
|
995
|
+
}
|
|
996
|
+
try {
|
|
997
|
+
strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
|
|
998
|
+
const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
|
|
999
|
+
releaseId
|
|
1000
|
+
);
|
|
1001
|
+
await strapi2.db.transaction(async () => {
|
|
1002
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
1003
|
+
await publishSingleTypeAction(uid, action, id);
|
|
1004
|
+
}
|
|
1005
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
1006
|
+
const uid = contentTypeUid;
|
|
1007
|
+
await publishCollectionTypeAction(
|
|
1008
|
+
uid,
|
|
1009
|
+
collectionTypeActions[uid].entriesToPublishIds,
|
|
1010
|
+
collectionTypeActions[uid].entriesToUnpublishIds
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1015
|
+
where: {
|
|
1016
|
+
id: releaseId
|
|
596
1017
|
},
|
|
597
|
-
|
|
1018
|
+
data: {
|
|
1019
|
+
status: "done",
|
|
1020
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
1024
|
+
isPublished: true,
|
|
1025
|
+
release: release22
|
|
1026
|
+
});
|
|
1027
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
1028
|
+
return { release: release22, error: null };
|
|
1029
|
+
} catch (error2) {
|
|
1030
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
1031
|
+
isPublished: false,
|
|
1032
|
+
error: error2
|
|
1033
|
+
});
|
|
1034
|
+
await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
|
|
1035
|
+
status: "failed"
|
|
1036
|
+
}).transacting(trx).execute();
|
|
1037
|
+
return {
|
|
1038
|
+
release: null,
|
|
1039
|
+
error: error2
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
if (error) {
|
|
1044
|
+
throw error;
|
|
1045
|
+
}
|
|
1046
|
+
return release2;
|
|
1047
|
+
},
|
|
1048
|
+
async updateAction(actionId, releaseId, update) {
|
|
1049
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1050
|
+
where: {
|
|
1051
|
+
id: actionId,
|
|
1052
|
+
release: {
|
|
1053
|
+
id: releaseId,
|
|
1054
|
+
releasedAt: {
|
|
1055
|
+
$null: true
|
|
1056
|
+
}
|
|
598
1057
|
}
|
|
1058
|
+
},
|
|
1059
|
+
data: update
|
|
1060
|
+
});
|
|
1061
|
+
if (!updatedAction) {
|
|
1062
|
+
throw new utils.errors.NotFoundError(
|
|
1063
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
599
1064
|
);
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
1065
|
+
}
|
|
1066
|
+
return updatedAction;
|
|
1067
|
+
},
|
|
1068
|
+
async deleteAction(actionId, releaseId) {
|
|
1069
|
+
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
1070
|
+
where: {
|
|
1071
|
+
id: actionId,
|
|
1072
|
+
release: {
|
|
1073
|
+
id: releaseId,
|
|
1074
|
+
releasedAt: {
|
|
1075
|
+
$null: true
|
|
1076
|
+
}
|
|
609
1077
|
}
|
|
610
|
-
);
|
|
611
|
-
if (entriesToPublish.length > 0) {
|
|
612
|
-
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
613
|
-
}
|
|
614
|
-
if (entriesToUnpublish.length > 0) {
|
|
615
|
-
await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
|
|
616
1078
|
}
|
|
1079
|
+
});
|
|
1080
|
+
if (!deletedAction) {
|
|
1081
|
+
throw new utils.errors.NotFoundError(
|
|
1082
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1083
|
+
);
|
|
617
1084
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
});
|
|
628
|
-
return release2;
|
|
629
|
-
},
|
|
630
|
-
async updateAction(actionId, releaseId, update) {
|
|
631
|
-
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
632
|
-
where: {
|
|
633
|
-
id: actionId,
|
|
634
|
-
release: {
|
|
635
|
-
id: releaseId,
|
|
636
|
-
releasedAt: {
|
|
637
|
-
$null: true
|
|
1085
|
+
this.updateReleaseStatus(releaseId);
|
|
1086
|
+
return deletedAction;
|
|
1087
|
+
},
|
|
1088
|
+
async updateReleaseStatus(releaseId) {
|
|
1089
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
1090
|
+
this.countActions({
|
|
1091
|
+
filters: {
|
|
1092
|
+
release: releaseId
|
|
638
1093
|
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
throw new utils.errors.NotFoundError(
|
|
645
|
-
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
646
|
-
);
|
|
647
|
-
}
|
|
648
|
-
return updatedAction;
|
|
649
|
-
},
|
|
650
|
-
async deleteAction(actionId, releaseId) {
|
|
651
|
-
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
652
|
-
where: {
|
|
653
|
-
id: actionId,
|
|
654
|
-
release: {
|
|
655
|
-
id: releaseId,
|
|
656
|
-
releasedAt: {
|
|
657
|
-
$null: true
|
|
1094
|
+
}),
|
|
1095
|
+
this.countActions({
|
|
1096
|
+
filters: {
|
|
1097
|
+
release: releaseId,
|
|
1098
|
+
isEntryValid: false
|
|
658
1099
|
}
|
|
1100
|
+
})
|
|
1101
|
+
]);
|
|
1102
|
+
if (totalActions > 0) {
|
|
1103
|
+
if (invalidActions > 0) {
|
|
1104
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1105
|
+
where: {
|
|
1106
|
+
id: releaseId
|
|
1107
|
+
},
|
|
1108
|
+
data: {
|
|
1109
|
+
status: "blocked"
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
659
1112
|
}
|
|
1113
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1114
|
+
where: {
|
|
1115
|
+
id: releaseId
|
|
1116
|
+
},
|
|
1117
|
+
data: {
|
|
1118
|
+
status: "ready"
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
660
1121
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
1122
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1123
|
+
where: {
|
|
1124
|
+
id: releaseId
|
|
1125
|
+
},
|
|
1126
|
+
data: {
|
|
1127
|
+
status: "empty"
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
666
1130
|
}
|
|
667
|
-
|
|
1131
|
+
};
|
|
1132
|
+
};
|
|
1133
|
+
class AlreadyOnReleaseError extends utils.errors.ApplicationError {
|
|
1134
|
+
constructor(message) {
|
|
1135
|
+
super(message);
|
|
1136
|
+
this.name = "AlreadyOnReleaseError";
|
|
668
1137
|
}
|
|
669
|
-
}
|
|
1138
|
+
}
|
|
670
1139
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
671
1140
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
672
1141
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
@@ -679,7 +1148,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
679
1148
|
(action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
|
|
680
1149
|
);
|
|
681
1150
|
if (isEntryInRelease) {
|
|
682
|
-
throw new
|
|
1151
|
+
throw new AlreadyOnReleaseError(
|
|
683
1152
|
`Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
|
|
684
1153
|
);
|
|
685
1154
|
}
|
|
@@ -711,27 +1180,103 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
711
1180
|
throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
|
|
712
1181
|
}
|
|
713
1182
|
},
|
|
714
|
-
async validateUniqueNameForPendingRelease(name) {
|
|
1183
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
715
1184
|
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
716
1185
|
filters: {
|
|
717
1186
|
releasedAt: {
|
|
718
1187
|
$null: true
|
|
719
1188
|
},
|
|
720
|
-
name
|
|
1189
|
+
name,
|
|
1190
|
+
...id && { id: { $ne: id } }
|
|
721
1191
|
}
|
|
722
1192
|
});
|
|
723
1193
|
const isNameUnique = pendingReleases.length === 0;
|
|
724
1194
|
if (!isNameUnique) {
|
|
725
1195
|
throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
|
|
726
1196
|
}
|
|
1197
|
+
},
|
|
1198
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
1199
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
1200
|
+
throw new utils.errors.ValidationError("Scheduled at must be later than now");
|
|
1201
|
+
}
|
|
727
1202
|
}
|
|
728
1203
|
});
|
|
1204
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
1205
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
1206
|
+
return {
|
|
1207
|
+
async set(releaseId, scheduleDate) {
|
|
1208
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
1209
|
+
if (!release2) {
|
|
1210
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1211
|
+
}
|
|
1212
|
+
const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
|
|
1213
|
+
try {
|
|
1214
|
+
await getService("release").publish(releaseId);
|
|
1215
|
+
} catch (error) {
|
|
1216
|
+
}
|
|
1217
|
+
this.cancel(releaseId);
|
|
1218
|
+
});
|
|
1219
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1220
|
+
this.cancel(releaseId);
|
|
1221
|
+
}
|
|
1222
|
+
scheduledJobs.set(releaseId, job);
|
|
1223
|
+
return scheduledJobs;
|
|
1224
|
+
},
|
|
1225
|
+
cancel(releaseId) {
|
|
1226
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1227
|
+
scheduledJobs.get(releaseId).cancel();
|
|
1228
|
+
scheduledJobs.delete(releaseId);
|
|
1229
|
+
}
|
|
1230
|
+
return scheduledJobs;
|
|
1231
|
+
},
|
|
1232
|
+
getAll() {
|
|
1233
|
+
return scheduledJobs;
|
|
1234
|
+
},
|
|
1235
|
+
/**
|
|
1236
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
1237
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
1238
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
1239
|
+
*/
|
|
1240
|
+
async syncFromDatabase() {
|
|
1241
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1242
|
+
where: {
|
|
1243
|
+
scheduledAt: {
|
|
1244
|
+
$gte: /* @__PURE__ */ new Date()
|
|
1245
|
+
},
|
|
1246
|
+
releasedAt: null
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
for (const release2 of releases) {
|
|
1250
|
+
this.set(release2.id, release2.scheduledAt);
|
|
1251
|
+
}
|
|
1252
|
+
return scheduledJobs;
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
};
|
|
729
1256
|
const services = {
|
|
730
1257
|
release: createReleaseService,
|
|
731
|
-
"release-validation": createReleaseValidationService
|
|
1258
|
+
"release-validation": createReleaseValidationService,
|
|
1259
|
+
scheduling: createSchedulingService
|
|
732
1260
|
};
|
|
733
1261
|
const RELEASE_SCHEMA = yup__namespace.object().shape({
|
|
734
|
-
name: yup__namespace.string().trim().required()
|
|
1262
|
+
name: yup__namespace.string().trim().required(),
|
|
1263
|
+
scheduledAt: yup__namespace.string().nullable(),
|
|
1264
|
+
isScheduled: yup__namespace.boolean().optional(),
|
|
1265
|
+
time: yup__namespace.string().when("isScheduled", {
|
|
1266
|
+
is: true,
|
|
1267
|
+
then: yup__namespace.string().trim().required(),
|
|
1268
|
+
otherwise: yup__namespace.string().nullable()
|
|
1269
|
+
}),
|
|
1270
|
+
timezone: yup__namespace.string().when("isScheduled", {
|
|
1271
|
+
is: true,
|
|
1272
|
+
then: yup__namespace.string().required().nullable(),
|
|
1273
|
+
otherwise: yup__namespace.string().nullable()
|
|
1274
|
+
}),
|
|
1275
|
+
date: yup__namespace.string().when("isScheduled", {
|
|
1276
|
+
is: true,
|
|
1277
|
+
then: yup__namespace.string().required().nullable(),
|
|
1278
|
+
otherwise: yup__namespace.string().nullable()
|
|
1279
|
+
})
|
|
735
1280
|
}).required().noUnknown();
|
|
736
1281
|
const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
|
|
737
1282
|
const releaseController = {
|
|
@@ -764,26 +1309,30 @@ const releaseController = {
|
|
|
764
1309
|
}
|
|
765
1310
|
};
|
|
766
1311
|
});
|
|
767
|
-
|
|
1312
|
+
const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
|
|
1313
|
+
where: {
|
|
1314
|
+
releasedAt: null
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
768
1318
|
}
|
|
769
1319
|
},
|
|
770
1320
|
async findOne(ctx) {
|
|
771
1321
|
const id = ctx.params.id;
|
|
772
1322
|
const releaseService = getService("release", { strapi });
|
|
773
1323
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
});
|
|
778
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
1324
|
+
if (!release2) {
|
|
1325
|
+
throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
|
|
1326
|
+
}
|
|
779
1327
|
const count = await releaseService.countActions({
|
|
780
1328
|
filters: {
|
|
781
1329
|
release: id
|
|
782
1330
|
}
|
|
783
1331
|
});
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
1332
|
+
const sanitizedRelease = {
|
|
1333
|
+
...release2,
|
|
1334
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
1335
|
+
};
|
|
787
1336
|
const data = {
|
|
788
1337
|
...sanitizedRelease,
|
|
789
1338
|
actions: {
|
|
@@ -794,6 +1343,33 @@ const releaseController = {
|
|
|
794
1343
|
};
|
|
795
1344
|
ctx.body = { data };
|
|
796
1345
|
},
|
|
1346
|
+
async mapEntriesToReleases(ctx) {
|
|
1347
|
+
const { contentTypeUid, entriesIds } = ctx.query;
|
|
1348
|
+
if (!contentTypeUid || !entriesIds) {
|
|
1349
|
+
throw new utils.errors.ValidationError("Missing required query parameters");
|
|
1350
|
+
}
|
|
1351
|
+
const releaseService = getService("release", { strapi });
|
|
1352
|
+
const releasesWithActions = await releaseService.findManyWithContentTypeEntryAttached(
|
|
1353
|
+
contentTypeUid,
|
|
1354
|
+
entriesIds
|
|
1355
|
+
);
|
|
1356
|
+
const mappedEntriesInReleases = releasesWithActions.reduce(
|
|
1357
|
+
(acc, release2) => {
|
|
1358
|
+
release2.actions.forEach((action) => {
|
|
1359
|
+
if (!acc[action.entry.id]) {
|
|
1360
|
+
acc[action.entry.id] = [{ id: release2.id, name: release2.name }];
|
|
1361
|
+
} else {
|
|
1362
|
+
acc[action.entry.id].push({ id: release2.id, name: release2.name });
|
|
1363
|
+
}
|
|
1364
|
+
});
|
|
1365
|
+
return acc;
|
|
1366
|
+
},
|
|
1367
|
+
{}
|
|
1368
|
+
);
|
|
1369
|
+
ctx.body = {
|
|
1370
|
+
data: mappedEntriesInReleases
|
|
1371
|
+
};
|
|
1372
|
+
},
|
|
797
1373
|
async create(ctx) {
|
|
798
1374
|
const user = ctx.state.user;
|
|
799
1375
|
const releaseArgs = ctx.request.body;
|
|
@@ -836,8 +1412,27 @@ const releaseController = {
|
|
|
836
1412
|
const id = ctx.params.id;
|
|
837
1413
|
const releaseService = getService("release", { strapi });
|
|
838
1414
|
const release2 = await releaseService.publish(id, { user });
|
|
1415
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1416
|
+
releaseService.countActions({
|
|
1417
|
+
filters: {
|
|
1418
|
+
release: id,
|
|
1419
|
+
type: "publish"
|
|
1420
|
+
}
|
|
1421
|
+
}),
|
|
1422
|
+
releaseService.countActions({
|
|
1423
|
+
filters: {
|
|
1424
|
+
release: id,
|
|
1425
|
+
type: "unpublish"
|
|
1426
|
+
}
|
|
1427
|
+
})
|
|
1428
|
+
]);
|
|
839
1429
|
ctx.body = {
|
|
840
|
-
data: release2
|
|
1430
|
+
data: release2,
|
|
1431
|
+
meta: {
|
|
1432
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1433
|
+
totalPublishedEntries: countPublishActions,
|
|
1434
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1435
|
+
}
|
|
841
1436
|
};
|
|
842
1437
|
}
|
|
843
1438
|
};
|
|
@@ -864,6 +1459,43 @@ const releaseActionController = {
|
|
|
864
1459
|
data: releaseAction2
|
|
865
1460
|
};
|
|
866
1461
|
},
|
|
1462
|
+
async createMany(ctx) {
|
|
1463
|
+
const releaseId = ctx.params.releaseId;
|
|
1464
|
+
const releaseActionsArgs = ctx.request.body;
|
|
1465
|
+
await Promise.all(
|
|
1466
|
+
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
|
1467
|
+
);
|
|
1468
|
+
const releaseService = getService("release", { strapi });
|
|
1469
|
+
const releaseActions = await strapi.db.transaction(async () => {
|
|
1470
|
+
const releaseActions2 = await Promise.all(
|
|
1471
|
+
releaseActionsArgs.map(async (releaseActionArgs) => {
|
|
1472
|
+
try {
|
|
1473
|
+
const action = await releaseService.createAction(releaseId, releaseActionArgs, {
|
|
1474
|
+
disableUpdateReleaseStatus: true
|
|
1475
|
+
});
|
|
1476
|
+
return action;
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
if (error instanceof AlreadyOnReleaseError) {
|
|
1479
|
+
return null;
|
|
1480
|
+
}
|
|
1481
|
+
throw error;
|
|
1482
|
+
}
|
|
1483
|
+
})
|
|
1484
|
+
);
|
|
1485
|
+
return releaseActions2;
|
|
1486
|
+
});
|
|
1487
|
+
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
|
1488
|
+
if (newReleaseActions.length > 0) {
|
|
1489
|
+
releaseService.updateReleaseStatus(releaseId);
|
|
1490
|
+
}
|
|
1491
|
+
ctx.body = {
|
|
1492
|
+
data: newReleaseActions,
|
|
1493
|
+
meta: {
|
|
1494
|
+
entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
|
|
1495
|
+
totalEntries: releaseActions.length
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1498
|
+
},
|
|
867
1499
|
async findMany(ctx) {
|
|
868
1500
|
const releaseId = ctx.params.releaseId;
|
|
869
1501
|
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
|
@@ -932,6 +1564,22 @@ const controllers = { release: releaseController, "release-action": releaseActio
|
|
|
932
1564
|
const release = {
|
|
933
1565
|
type: "admin",
|
|
934
1566
|
routes: [
|
|
1567
|
+
{
|
|
1568
|
+
method: "GET",
|
|
1569
|
+
path: "/mapEntriesToReleases",
|
|
1570
|
+
handler: "release.mapEntriesToReleases",
|
|
1571
|
+
config: {
|
|
1572
|
+
policies: [
|
|
1573
|
+
"admin::isAuthenticatedAdmin",
|
|
1574
|
+
{
|
|
1575
|
+
name: "admin::hasPermissions",
|
|
1576
|
+
config: {
|
|
1577
|
+
actions: ["plugin::content-releases.read"]
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
]
|
|
1581
|
+
}
|
|
1582
|
+
},
|
|
935
1583
|
{
|
|
936
1584
|
method: "POST",
|
|
937
1585
|
path: "/",
|
|
@@ -1049,6 +1697,22 @@ const releaseAction = {
|
|
|
1049
1697
|
]
|
|
1050
1698
|
}
|
|
1051
1699
|
},
|
|
1700
|
+
{
|
|
1701
|
+
method: "POST",
|
|
1702
|
+
path: "/:releaseId/actions/bulk",
|
|
1703
|
+
handler: "release-action.createMany",
|
|
1704
|
+
config: {
|
|
1705
|
+
policies: [
|
|
1706
|
+
"admin::isAuthenticatedAdmin",
|
|
1707
|
+
{
|
|
1708
|
+
name: "admin::hasPermissions",
|
|
1709
|
+
config: {
|
|
1710
|
+
actions: ["plugin::content-releases.create-action"]
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
]
|
|
1714
|
+
}
|
|
1715
|
+
},
|
|
1052
1716
|
{
|
|
1053
1717
|
method: "GET",
|
|
1054
1718
|
path: "/:releaseId/actions",
|
|
@@ -1109,6 +1773,7 @@ const getPlugin = () => {
|
|
|
1109
1773
|
return {
|
|
1110
1774
|
register,
|
|
1111
1775
|
bootstrap,
|
|
1776
|
+
destroy,
|
|
1112
1777
|
contentTypes,
|
|
1113
1778
|
services,
|
|
1114
1779
|
controllers,
|
|
@@ -1116,6 +1781,9 @@ const getPlugin = () => {
|
|
|
1116
1781
|
};
|
|
1117
1782
|
}
|
|
1118
1783
|
return {
|
|
1784
|
+
// Always return register, it handles its own feature check
|
|
1785
|
+
register,
|
|
1786
|
+
// Always return contentTypes to avoid losing data when the feature is disabled
|
|
1119
1787
|
contentTypes
|
|
1120
1788
|
};
|
|
1121
1789
|
};
|