@strapi/content-releases 0.0.0-next.6d59515520a3850456f256fb0e4c54b75054ddf4 → 0.0.0-next.734763e5757af27ff96ad1c9662161f3f677052a
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-_20W9dYa.js → App-c5uGEz9O.js} +744 -446
- package/dist/_chunks/App-c5uGEz9O.js.map +1 -0
- package/dist/_chunks/{App-L1jSxCiL.mjs → App-xQ5ljY7-.mjs} +752 -455
- package/dist/_chunks/App-xQ5ljY7-.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-3SGjiVyR.js} +29 -7
- package/dist/_chunks/en-3SGjiVyR.js.map +1 -0
- package/dist/_chunks/{en-MyLPoISH.mjs → en-bpHsnU0n.mjs} +29 -7
- package/dist/_chunks/en-bpHsnU0n.mjs.map +1 -0
- package/dist/_chunks/{index-KJa1Rb5F.js → index-4U0Q_Fgd.js} +350 -34
- package/dist/_chunks/index-4U0Q_Fgd.js.map +1 -0
- package/dist/_chunks/{index-c4zRX_sg.mjs → index-ifoPtgmH.mjs} +363 -47
- package/dist/_chunks/index-ifoPtgmH.mjs.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +2 -2
- package/dist/server/index.js +998 -399
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +997 -399
- 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.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { contentTypes as contentTypes$1, mapAsync, setCreatorFields, errors, validateYupSchema, yup as yup$1 } from "@strapi/utils";
|
|
2
|
+
import isEqual from "lodash/isEqual";
|
|
2
3
|
import { difference, keys } from "lodash";
|
|
3
4
|
import _ from "lodash/fp";
|
|
4
5
|
import EE from "@strapi/strapi/dist/utils/ee";
|
|
6
|
+
import { scheduleJob } from "node-schedule";
|
|
5
7
|
import * as yup from "yup";
|
|
6
8
|
const RELEASE_MODEL_UID = "plugin::content-releases.release";
|
|
7
9
|
const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
|
|
@@ -49,6 +51,32 @@ const ACTIONS = [
|
|
|
49
51
|
pluginName: "content-releases"
|
|
50
52
|
}
|
|
51
53
|
];
|
|
54
|
+
const ALLOWED_WEBHOOK_EVENTS = {
|
|
55
|
+
RELEASES_PUBLISH: "releases.publish"
|
|
56
|
+
};
|
|
57
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
58
|
+
return strapi2.plugin("content-releases").service(name);
|
|
59
|
+
};
|
|
60
|
+
const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
61
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
62
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
63
|
+
const entry = await strapi2.entityService.findOne(contentTypeUid, entryId, { populate });
|
|
64
|
+
return entry;
|
|
65
|
+
};
|
|
66
|
+
const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
67
|
+
try {
|
|
68
|
+
await strapi2.entityValidator.validateEntityCreation(
|
|
69
|
+
strapi2.getModel(contentTypeUid),
|
|
70
|
+
entry,
|
|
71
|
+
void 0,
|
|
72
|
+
// @ts-expect-error - FIXME: entity here is unnecessary
|
|
73
|
+
entry
|
|
74
|
+
);
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
52
80
|
async function deleteActionsOnDisableDraftAndPublish({
|
|
53
81
|
oldContentTypes,
|
|
54
82
|
contentTypes: contentTypes2
|
|
@@ -75,28 +103,196 @@ async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes:
|
|
|
75
103
|
});
|
|
76
104
|
}
|
|
77
105
|
}
|
|
106
|
+
async function migrateIsValidAndStatusReleases() {
|
|
107
|
+
const releasesWithoutStatus = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
108
|
+
where: {
|
|
109
|
+
status: null,
|
|
110
|
+
releasedAt: null
|
|
111
|
+
},
|
|
112
|
+
populate: {
|
|
113
|
+
actions: {
|
|
114
|
+
populate: {
|
|
115
|
+
entry: true
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
mapAsync(releasesWithoutStatus, async (release2) => {
|
|
121
|
+
const actions = release2.actions;
|
|
122
|
+
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
123
|
+
for (const action of notValidatedActions) {
|
|
124
|
+
if (action.entry) {
|
|
125
|
+
const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
|
|
126
|
+
strapi
|
|
127
|
+
});
|
|
128
|
+
if (populatedEntry) {
|
|
129
|
+
const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
|
|
130
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
131
|
+
where: {
|
|
132
|
+
id: action.id
|
|
133
|
+
},
|
|
134
|
+
data: {
|
|
135
|
+
isEntryValid
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
142
|
+
});
|
|
143
|
+
const publishedReleases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
144
|
+
where: {
|
|
145
|
+
status: null,
|
|
146
|
+
releasedAt: {
|
|
147
|
+
$notNull: true
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
mapAsync(publishedReleases, async (release2) => {
|
|
152
|
+
return strapi.db.query(RELEASE_MODEL_UID).update({
|
|
153
|
+
where: {
|
|
154
|
+
id: release2.id
|
|
155
|
+
},
|
|
156
|
+
data: {
|
|
157
|
+
status: "done"
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
163
|
+
if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
|
|
164
|
+
const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
|
|
165
|
+
(uid) => oldContentTypes[uid]?.options?.draftAndPublish
|
|
166
|
+
);
|
|
167
|
+
const releasesAffected = /* @__PURE__ */ new Set();
|
|
168
|
+
mapAsync(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
|
|
169
|
+
const oldContentType = oldContentTypes[contentTypeUID];
|
|
170
|
+
const contentType = contentTypes2[contentTypeUID];
|
|
171
|
+
if (!isEqual(oldContentType?.attributes, contentType?.attributes)) {
|
|
172
|
+
const actions = await strapi.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
173
|
+
where: {
|
|
174
|
+
contentType: contentTypeUID
|
|
175
|
+
},
|
|
176
|
+
populate: {
|
|
177
|
+
entry: true,
|
|
178
|
+
release: true
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
await mapAsync(actions, async (action) => {
|
|
182
|
+
if (action.entry && action.release) {
|
|
183
|
+
const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
|
|
184
|
+
strapi
|
|
185
|
+
});
|
|
186
|
+
if (populatedEntry) {
|
|
187
|
+
const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
|
|
188
|
+
strapi
|
|
189
|
+
});
|
|
190
|
+
releasesAffected.add(action.release.id);
|
|
191
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
192
|
+
where: {
|
|
193
|
+
id: action.id
|
|
194
|
+
},
|
|
195
|
+
data: {
|
|
196
|
+
isEntryValid
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}).then(() => {
|
|
204
|
+
mapAsync(releasesAffected, async (releaseId) => {
|
|
205
|
+
return getService("release", { strapi }).updateReleaseStatus(releaseId);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
211
|
+
if (!oldContentTypes) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
for (const uid in contentTypes2) {
|
|
215
|
+
if (!oldContentTypes[uid]) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const oldContentType = oldContentTypes[uid];
|
|
219
|
+
const contentType = contentTypes2[uid];
|
|
220
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
221
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
222
|
+
if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
|
|
223
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
224
|
+
locale: null
|
|
225
|
+
}).where({ contentType: uid }).execute();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
230
|
+
if (!oldContentTypes) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
for (const uid in contentTypes2) {
|
|
234
|
+
if (!oldContentTypes[uid]) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const oldContentType = oldContentTypes[uid];
|
|
238
|
+
const contentType = contentTypes2[uid];
|
|
239
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
240
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
241
|
+
const { getDefaultLocale } = i18nPlugin.service("locales");
|
|
242
|
+
if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
|
|
243
|
+
const defaultLocale = await getDefaultLocale();
|
|
244
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
245
|
+
locale: defaultLocale
|
|
246
|
+
}).where({ contentType: uid }).execute();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
78
250
|
const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
|
|
79
251
|
const register = async ({ strapi: strapi2 }) => {
|
|
80
252
|
if (features$2.isEnabled("cms-content-releases")) {
|
|
81
253
|
await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
|
82
|
-
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
|
|
83
|
-
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
|
|
254
|
+
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish).register(disableContentTypeLocalized);
|
|
255
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
256
|
+
}
|
|
257
|
+
if (strapi2.plugin("graphql")) {
|
|
258
|
+
const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
|
|
259
|
+
graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
|
|
260
|
+
graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
|
|
84
261
|
}
|
|
85
262
|
};
|
|
86
263
|
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
87
264
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
88
265
|
if (features$1.isEnabled("cms-content-releases")) {
|
|
266
|
+
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
267
|
+
(uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
|
|
268
|
+
);
|
|
89
269
|
strapi2.db.lifecycles.subscribe({
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
270
|
+
models: contentTypesWithDraftAndPublish,
|
|
271
|
+
async afterDelete(event) {
|
|
272
|
+
try {
|
|
273
|
+
const { model, result } = event;
|
|
274
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
275
|
+
const { id } = result;
|
|
276
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
277
|
+
where: {
|
|
278
|
+
actions: {
|
|
279
|
+
target_type: model.uid,
|
|
280
|
+
target_id: id
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
285
|
+
where: {
|
|
286
|
+
target_type: model.uid,
|
|
287
|
+
target_id: id
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
for (const release2 of releases) {
|
|
291
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
98
292
|
}
|
|
99
|
-
}
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
100
296
|
}
|
|
101
297
|
},
|
|
102
298
|
/**
|
|
@@ -116,20 +312,94 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
116
312
|
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
117
313
|
*/
|
|
118
314
|
async afterDeleteMany(event) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
315
|
+
try {
|
|
316
|
+
const { model, state } = event;
|
|
317
|
+
const entriesToDelete = state.entriesToDelete;
|
|
318
|
+
if (entriesToDelete) {
|
|
319
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
320
|
+
where: {
|
|
321
|
+
actions: {
|
|
322
|
+
target_type: model.uid,
|
|
323
|
+
target_id: {
|
|
324
|
+
$in: entriesToDelete.map(
|
|
325
|
+
(entry) => entry.id
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
332
|
+
where: {
|
|
333
|
+
target_type: model.uid,
|
|
334
|
+
target_id: {
|
|
335
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
336
|
+
}
|
|
127
337
|
}
|
|
338
|
+
});
|
|
339
|
+
for (const release2 of releases) {
|
|
340
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
128
341
|
}
|
|
342
|
+
}
|
|
343
|
+
} catch (error) {
|
|
344
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
345
|
+
error
|
|
129
346
|
});
|
|
130
347
|
}
|
|
348
|
+
},
|
|
349
|
+
async afterUpdate(event) {
|
|
350
|
+
try {
|
|
351
|
+
const { model, result } = event;
|
|
352
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
353
|
+
const isEntryValid = await getEntryValidStatus(
|
|
354
|
+
model.uid,
|
|
355
|
+
result,
|
|
356
|
+
{
|
|
357
|
+
strapi: strapi2
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
361
|
+
where: {
|
|
362
|
+
target_type: model.uid,
|
|
363
|
+
target_id: result.id
|
|
364
|
+
},
|
|
365
|
+
data: {
|
|
366
|
+
isEntryValid
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
370
|
+
where: {
|
|
371
|
+
actions: {
|
|
372
|
+
target_type: model.uid,
|
|
373
|
+
target_id: result.id
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
for (const release2 of releases) {
|
|
378
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
} catch (error) {
|
|
382
|
+
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
383
|
+
}
|
|
131
384
|
}
|
|
132
385
|
});
|
|
386
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
387
|
+
strapi2.log.error(
|
|
388
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
389
|
+
);
|
|
390
|
+
throw err;
|
|
391
|
+
});
|
|
392
|
+
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
393
|
+
strapi2.webhookStore.addAllowedEvent(key, value);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
const destroy = async ({ strapi: strapi2 }) => {
|
|
398
|
+
const scheduledJobs = getService("scheduling", {
|
|
399
|
+
strapi: strapi2
|
|
400
|
+
}).getAll();
|
|
401
|
+
for (const [, job] of scheduledJobs) {
|
|
402
|
+
job.cancel();
|
|
133
403
|
}
|
|
134
404
|
};
|
|
135
405
|
const schema$1 = {
|
|
@@ -158,6 +428,17 @@ const schema$1 = {
|
|
|
158
428
|
releasedAt: {
|
|
159
429
|
type: "datetime"
|
|
160
430
|
},
|
|
431
|
+
scheduledAt: {
|
|
432
|
+
type: "datetime"
|
|
433
|
+
},
|
|
434
|
+
timezone: {
|
|
435
|
+
type: "string"
|
|
436
|
+
},
|
|
437
|
+
status: {
|
|
438
|
+
type: "enumeration",
|
|
439
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
440
|
+
required: true
|
|
441
|
+
},
|
|
161
442
|
actions: {
|
|
162
443
|
type: "relation",
|
|
163
444
|
relation: "oneToMany",
|
|
@@ -210,6 +491,9 @@ const schema = {
|
|
|
210
491
|
relation: "manyToOne",
|
|
211
492
|
target: RELEASE_MODEL_UID,
|
|
212
493
|
inversedBy: "actions"
|
|
494
|
+
},
|
|
495
|
+
isEntryValid: {
|
|
496
|
+
type: "boolean"
|
|
213
497
|
}
|
|
214
498
|
}
|
|
215
499
|
};
|
|
@@ -220,9 +504,6 @@ const contentTypes = {
|
|
|
220
504
|
release: release$1,
|
|
221
505
|
"release-action": releaseAction$1
|
|
222
506
|
};
|
|
223
|
-
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
224
|
-
return strapi2.plugin("content-releases").service(name);
|
|
225
|
-
};
|
|
226
507
|
const getGroupName = (queryValue) => {
|
|
227
508
|
switch (queryValue) {
|
|
228
509
|
case "contentType":
|
|
@@ -235,415 +516,581 @@ const getGroupName = (queryValue) => {
|
|
|
235
516
|
return "contentType.displayName";
|
|
236
517
|
}
|
|
237
518
|
};
|
|
238
|
-
const createReleaseService = ({ strapi: strapi2 }) =>
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
);
|
|
245
|
-
await Promise.all([
|
|
246
|
-
validatePendingReleasesLimit(),
|
|
247
|
-
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name)
|
|
248
|
-
]);
|
|
249
|
-
return strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
250
|
-
data: releaseWithCreatorFields
|
|
251
|
-
});
|
|
252
|
-
},
|
|
253
|
-
async findOne(id, query = {}) {
|
|
254
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
255
|
-
...query
|
|
256
|
-
});
|
|
257
|
-
return release2;
|
|
258
|
-
},
|
|
259
|
-
findPage(query) {
|
|
260
|
-
return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
|
|
261
|
-
...query,
|
|
262
|
-
populate: {
|
|
263
|
-
actions: {
|
|
264
|
-
// @ts-expect-error Ignore missing properties
|
|
265
|
-
count: true
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
},
|
|
270
|
-
async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
271
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
272
|
-
where: {
|
|
273
|
-
actions: {
|
|
274
|
-
target_type: contentTypeUid,
|
|
275
|
-
target_id: entryId
|
|
276
|
-
},
|
|
277
|
-
releasedAt: {
|
|
278
|
-
$null: true
|
|
279
|
-
}
|
|
280
|
-
},
|
|
281
|
-
populate: {
|
|
282
|
-
// Filter the action to get only the content type entry
|
|
283
|
-
actions: {
|
|
284
|
-
where: {
|
|
285
|
-
target_type: contentTypeUid,
|
|
286
|
-
target_id: entryId
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
return releases.map((release2) => {
|
|
292
|
-
if (release2.actions?.length) {
|
|
293
|
-
const [actionForEntry] = release2.actions;
|
|
294
|
-
delete release2.actions;
|
|
295
|
-
return {
|
|
296
|
-
...release2,
|
|
297
|
-
action: actionForEntry
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
return release2;
|
|
301
|
-
});
|
|
302
|
-
},
|
|
303
|
-
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
304
|
-
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
305
|
-
where: {
|
|
306
|
-
releasedAt: {
|
|
307
|
-
$null: true
|
|
308
|
-
},
|
|
309
|
-
actions: {
|
|
310
|
-
target_type: contentTypeUid,
|
|
311
|
-
target_id: entryId
|
|
312
|
-
}
|
|
313
|
-
}
|
|
519
|
+
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
520
|
+
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
521
|
+
strapi2.eventHub.emit(event, {
|
|
522
|
+
isPublished,
|
|
523
|
+
error,
|
|
524
|
+
release: release2
|
|
314
525
|
});
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
],
|
|
327
|
-
releasedAt: {
|
|
328
|
-
$null: true
|
|
329
|
-
}
|
|
526
|
+
};
|
|
527
|
+
const publishSingleTypeAction = async (uid, actionType, entryId) => {
|
|
528
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
529
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
530
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
531
|
+
const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
|
|
532
|
+
try {
|
|
533
|
+
if (actionType === "publish") {
|
|
534
|
+
await entityManagerService.publish(entry, uid);
|
|
535
|
+
} else {
|
|
536
|
+
await entityManagerService.unpublish(entry, uid);
|
|
330
537
|
}
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
return {
|
|
337
|
-
...release2,
|
|
338
|
-
action: actionForEntry
|
|
339
|
-
};
|
|
538
|
+
} catch (error) {
|
|
539
|
+
if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
540
|
+
;
|
|
541
|
+
else {
|
|
542
|
+
throw error;
|
|
340
543
|
}
|
|
341
|
-
return release2;
|
|
342
|
-
});
|
|
343
|
-
},
|
|
344
|
-
async update(id, releaseData, { user }) {
|
|
345
|
-
const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(releaseData);
|
|
346
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
347
|
-
if (!release2) {
|
|
348
|
-
throw new errors.NotFoundError(`No release found for id ${id}`);
|
|
349
|
-
}
|
|
350
|
-
if (release2.releasedAt) {
|
|
351
|
-
throw new errors.ValidationError("Release already published");
|
|
352
|
-
}
|
|
353
|
-
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
354
|
-
/*
|
|
355
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
356
|
-
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
357
|
-
*/
|
|
358
|
-
// @ts-expect-error see above
|
|
359
|
-
data: releaseWithCreatorFields
|
|
360
|
-
});
|
|
361
|
-
return updatedRelease;
|
|
362
|
-
},
|
|
363
|
-
async createAction(releaseId, action) {
|
|
364
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
365
|
-
strapi: strapi2
|
|
366
|
-
});
|
|
367
|
-
await Promise.all([
|
|
368
|
-
validateEntryContentType(action.entry.contentType),
|
|
369
|
-
validateUniqueEntry(releaseId, action)
|
|
370
|
-
]);
|
|
371
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
372
|
-
if (!release2) {
|
|
373
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
374
|
-
}
|
|
375
|
-
if (release2.releasedAt) {
|
|
376
|
-
throw new errors.ValidationError("Release already published");
|
|
377
544
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
__pivot: { field: "entry" }
|
|
388
|
-
},
|
|
389
|
-
release: releaseId
|
|
390
|
-
},
|
|
391
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
392
|
-
});
|
|
393
|
-
},
|
|
394
|
-
async findActions(releaseId, query) {
|
|
395
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
396
|
-
fields: ["id"]
|
|
397
|
-
});
|
|
398
|
-
if (!release2) {
|
|
399
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
400
|
-
}
|
|
401
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
402
|
-
...query,
|
|
403
|
-
populate: {
|
|
404
|
-
entry: {
|
|
405
|
-
populate: "*"
|
|
545
|
+
};
|
|
546
|
+
const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
|
|
547
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
548
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
549
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
550
|
+
const entriesToPublish = await strapi2.entityService.findMany(uid, {
|
|
551
|
+
filters: {
|
|
552
|
+
id: {
|
|
553
|
+
$in: entriesToPublishIds
|
|
406
554
|
}
|
|
407
555
|
},
|
|
408
|
-
|
|
409
|
-
release: releaseId
|
|
410
|
-
}
|
|
556
|
+
populate
|
|
411
557
|
});
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
async groupActions(actions, groupBy) {
|
|
417
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
418
|
-
if (!acc.includes(action.contentType)) {
|
|
419
|
-
acc.push(action.contentType);
|
|
420
|
-
}
|
|
421
|
-
return acc;
|
|
422
|
-
}, []);
|
|
423
|
-
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
|
|
424
|
-
contentTypeUids
|
|
425
|
-
);
|
|
426
|
-
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
427
|
-
const formattedData = actions.map((action) => {
|
|
428
|
-
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
429
|
-
return {
|
|
430
|
-
...action,
|
|
431
|
-
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
432
|
-
contentType: {
|
|
433
|
-
displayName,
|
|
434
|
-
mainFieldValue: action.entry[mainField],
|
|
435
|
-
uid: action.contentType
|
|
558
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
|
|
559
|
+
filters: {
|
|
560
|
+
id: {
|
|
561
|
+
$in: entriestoUnpublishIds
|
|
436
562
|
}
|
|
437
|
-
}
|
|
563
|
+
},
|
|
564
|
+
populate
|
|
438
565
|
});
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
},
|
|
442
|
-
async getLocalesDataForActions() {
|
|
443
|
-
if (!strapi2.plugin("i18n")) {
|
|
444
|
-
return {};
|
|
566
|
+
if (entriesToPublish.length > 0) {
|
|
567
|
+
await entityManagerService.publishMany(entriesToPublish, uid);
|
|
445
568
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
449
|
-
return acc;
|
|
450
|
-
}, {});
|
|
451
|
-
},
|
|
452
|
-
async getContentTypesDataForActions(contentTypesUids) {
|
|
453
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
454
|
-
const contentTypesData = {};
|
|
455
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
456
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
457
|
-
uid: contentTypeUid
|
|
458
|
-
});
|
|
459
|
-
contentTypesData[contentTypeUid] = {
|
|
460
|
-
mainField: contentTypeConfig.settings.mainField,
|
|
461
|
-
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
462
|
-
};
|
|
569
|
+
if (entriesToUnpublish.length > 0) {
|
|
570
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, uid);
|
|
463
571
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
return acc;
|
|
472
|
-
}, []);
|
|
473
|
-
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
474
|
-
(acc, contentTypeUid) => {
|
|
475
|
-
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
476
|
-
return acc;
|
|
477
|
-
},
|
|
478
|
-
{}
|
|
479
|
-
);
|
|
480
|
-
return contentTypeModelsMap;
|
|
481
|
-
},
|
|
482
|
-
async getAllComponents() {
|
|
483
|
-
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
484
|
-
const components = await contentManagerComponentsService.findAllComponents();
|
|
485
|
-
const componentsMap = components.reduce(
|
|
486
|
-
(acc, component) => {
|
|
487
|
-
acc[component.uid] = component;
|
|
488
|
-
return acc;
|
|
572
|
+
};
|
|
573
|
+
const getFormattedActions = async (releaseId) => {
|
|
574
|
+
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
575
|
+
where: {
|
|
576
|
+
release: {
|
|
577
|
+
id: releaseId
|
|
578
|
+
}
|
|
489
579
|
},
|
|
490
|
-
{}
|
|
491
|
-
);
|
|
492
|
-
return componentsMap;
|
|
493
|
-
},
|
|
494
|
-
async delete(releaseId) {
|
|
495
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
496
580
|
populate: {
|
|
497
|
-
|
|
581
|
+
entry: {
|
|
498
582
|
fields: ["id"]
|
|
499
583
|
}
|
|
500
584
|
}
|
|
501
585
|
});
|
|
502
|
-
if (
|
|
503
|
-
throw new errors.
|
|
586
|
+
if (actions.length === 0) {
|
|
587
|
+
throw new errors.ValidationError("No entries to publish");
|
|
504
588
|
}
|
|
505
|
-
|
|
506
|
-
|
|
589
|
+
const collectionTypeActions = {};
|
|
590
|
+
const singleTypeActions = [];
|
|
591
|
+
for (const action of actions) {
|
|
592
|
+
const contentTypeUid = action.contentType;
|
|
593
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
594
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
595
|
+
collectionTypeActions[contentTypeUid] = {
|
|
596
|
+
entriesToPublishIds: [],
|
|
597
|
+
entriesToUnpublishIds: []
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
if (action.type === "publish") {
|
|
601
|
+
collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
|
|
602
|
+
} else {
|
|
603
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
604
|
+
}
|
|
605
|
+
} else {
|
|
606
|
+
singleTypeActions.push({
|
|
607
|
+
uid: contentTypeUid,
|
|
608
|
+
action: action.type,
|
|
609
|
+
id: action.entry.id
|
|
610
|
+
});
|
|
611
|
+
}
|
|
507
612
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
613
|
+
return { collectionTypeActions, singleTypeActions };
|
|
614
|
+
};
|
|
615
|
+
return {
|
|
616
|
+
async create(releaseData, { user }) {
|
|
617
|
+
const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
|
|
618
|
+
const {
|
|
619
|
+
validatePendingReleasesLimit,
|
|
620
|
+
validateUniqueNameForPendingRelease,
|
|
621
|
+
validateScheduledAtIsLaterThanNow
|
|
622
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
623
|
+
await Promise.all([
|
|
624
|
+
validatePendingReleasesLimit(),
|
|
625
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
626
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
627
|
+
]);
|
|
628
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
629
|
+
data: {
|
|
630
|
+
...releaseWithCreatorFields,
|
|
631
|
+
status: "empty"
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
if (releaseWithCreatorFields.scheduledAt) {
|
|
635
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
636
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
637
|
+
}
|
|
638
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
639
|
+
return release2;
|
|
640
|
+
},
|
|
641
|
+
async findOne(id, query = {}) {
|
|
642
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
643
|
+
...query
|
|
644
|
+
});
|
|
645
|
+
return release2;
|
|
646
|
+
},
|
|
647
|
+
findPage(query) {
|
|
648
|
+
return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
|
|
649
|
+
...query,
|
|
650
|
+
populate: {
|
|
651
|
+
actions: {
|
|
652
|
+
// @ts-expect-error Ignore missing properties
|
|
653
|
+
count: true
|
|
513
654
|
}
|
|
514
655
|
}
|
|
515
656
|
});
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
657
|
+
},
|
|
658
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
659
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
660
|
+
where: {
|
|
661
|
+
actions: {
|
|
662
|
+
target_type: contentTypeUid,
|
|
663
|
+
target_id: entryId
|
|
664
|
+
},
|
|
665
|
+
releasedAt: {
|
|
666
|
+
$null: true
|
|
667
|
+
}
|
|
668
|
+
},
|
|
525
669
|
populate: {
|
|
670
|
+
// Filter the action to get only the content type entry
|
|
526
671
|
actions: {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
}
|
|
672
|
+
where: {
|
|
673
|
+
target_type: contentTypeUid,
|
|
674
|
+
target_id: entryId
|
|
531
675
|
}
|
|
532
676
|
}
|
|
533
677
|
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
} else {
|
|
557
|
-
actions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
561
|
-
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
562
|
-
await strapi2.db.transaction(async () => {
|
|
563
|
-
for (const contentTypeUid of Object.keys(actions)) {
|
|
564
|
-
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
565
|
-
const { entriestoPublishIds, entriesToUnpublishIds } = actions[contentTypeUid];
|
|
566
|
-
const entriesToPublish = await strapi2.entityService.findMany(
|
|
567
|
-
contentTypeUid,
|
|
568
|
-
{
|
|
569
|
-
filters: {
|
|
570
|
-
id: {
|
|
571
|
-
$in: entriestoPublishIds
|
|
572
|
-
}
|
|
573
|
-
},
|
|
574
|
-
populate
|
|
678
|
+
});
|
|
679
|
+
return releases.map((release2) => {
|
|
680
|
+
if (release2.actions?.length) {
|
|
681
|
+
const [actionForEntry] = release2.actions;
|
|
682
|
+
delete release2.actions;
|
|
683
|
+
return {
|
|
684
|
+
...release2,
|
|
685
|
+
action: actionForEntry
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
return release2;
|
|
689
|
+
});
|
|
690
|
+
},
|
|
691
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
692
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
693
|
+
where: {
|
|
694
|
+
releasedAt: {
|
|
695
|
+
$null: true
|
|
696
|
+
},
|
|
697
|
+
actions: {
|
|
698
|
+
target_type: contentTypeUid,
|
|
699
|
+
target_id: entryId
|
|
575
700
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
704
|
+
where: {
|
|
705
|
+
$or: [
|
|
706
|
+
{
|
|
581
707
|
id: {
|
|
582
|
-
$
|
|
708
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
583
709
|
}
|
|
584
710
|
},
|
|
585
|
-
|
|
711
|
+
{
|
|
712
|
+
actions: null
|
|
713
|
+
}
|
|
714
|
+
],
|
|
715
|
+
releasedAt: {
|
|
716
|
+
$null: true
|
|
586
717
|
}
|
|
587
|
-
);
|
|
588
|
-
if (entriesToPublish.length > 0) {
|
|
589
|
-
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
590
718
|
}
|
|
591
|
-
|
|
592
|
-
|
|
719
|
+
});
|
|
720
|
+
return releases.map((release2) => {
|
|
721
|
+
if (release2.actions?.length) {
|
|
722
|
+
const [actionForEntry] = release2.actions;
|
|
723
|
+
delete release2.actions;
|
|
724
|
+
return {
|
|
725
|
+
...release2,
|
|
726
|
+
action: actionForEntry
|
|
727
|
+
};
|
|
593
728
|
}
|
|
729
|
+
return release2;
|
|
730
|
+
});
|
|
731
|
+
},
|
|
732
|
+
async update(id, releaseData, { user }) {
|
|
733
|
+
const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(
|
|
734
|
+
releaseData
|
|
735
|
+
);
|
|
736
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
737
|
+
"release-validation",
|
|
738
|
+
{ strapi: strapi2 }
|
|
739
|
+
);
|
|
740
|
+
await Promise.all([
|
|
741
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
742
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
743
|
+
]);
|
|
744
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
745
|
+
if (!release2) {
|
|
746
|
+
throw new errors.NotFoundError(`No release found for id ${id}`);
|
|
594
747
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
748
|
+
if (release2.releasedAt) {
|
|
749
|
+
throw new errors.ValidationError("Release already published");
|
|
750
|
+
}
|
|
751
|
+
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
598
752
|
/*
|
|
599
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
753
|
+
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
754
|
+
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
600
755
|
*/
|
|
601
756
|
// @ts-expect-error see above
|
|
602
|
-
|
|
757
|
+
data: releaseWithCreatorFields
|
|
758
|
+
});
|
|
759
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
760
|
+
if (releaseData.scheduledAt) {
|
|
761
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
762
|
+
} else if (release2.scheduledAt) {
|
|
763
|
+
schedulingService.cancel(id);
|
|
603
764
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
765
|
+
this.updateReleaseStatus(id);
|
|
766
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
767
|
+
return updatedRelease;
|
|
768
|
+
},
|
|
769
|
+
async createAction(releaseId, action) {
|
|
770
|
+
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
771
|
+
strapi: strapi2
|
|
772
|
+
});
|
|
773
|
+
await Promise.all([
|
|
774
|
+
validateEntryContentType(action.entry.contentType),
|
|
775
|
+
validateUniqueEntry(releaseId, action)
|
|
776
|
+
]);
|
|
777
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
778
|
+
if (!release2) {
|
|
779
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
780
|
+
}
|
|
781
|
+
if (release2.releasedAt) {
|
|
782
|
+
throw new errors.ValidationError("Release already published");
|
|
783
|
+
}
|
|
784
|
+
const { entry, type } = action;
|
|
785
|
+
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
786
|
+
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
787
|
+
const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
788
|
+
data: {
|
|
789
|
+
type,
|
|
790
|
+
contentType: entry.contentType,
|
|
791
|
+
locale: entry.locale,
|
|
792
|
+
isEntryValid,
|
|
793
|
+
entry: {
|
|
794
|
+
id: entry.id,
|
|
795
|
+
__type: entry.contentType,
|
|
796
|
+
__pivot: { field: "entry" }
|
|
797
|
+
},
|
|
798
|
+
release: releaseId
|
|
799
|
+
},
|
|
800
|
+
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
801
|
+
});
|
|
802
|
+
this.updateReleaseStatus(releaseId);
|
|
803
|
+
return releaseAction2;
|
|
804
|
+
},
|
|
805
|
+
async findActions(releaseId, query) {
|
|
806
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
807
|
+
fields: ["id"]
|
|
808
|
+
});
|
|
809
|
+
if (!release2) {
|
|
810
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
811
|
+
}
|
|
812
|
+
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
813
|
+
...query,
|
|
814
|
+
populate: {
|
|
815
|
+
entry: {
|
|
816
|
+
populate: "*"
|
|
615
817
|
}
|
|
818
|
+
},
|
|
819
|
+
filters: {
|
|
820
|
+
release: releaseId
|
|
616
821
|
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
822
|
+
});
|
|
823
|
+
},
|
|
824
|
+
async countActions(query) {
|
|
825
|
+
return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
|
|
826
|
+
},
|
|
827
|
+
async groupActions(actions, groupBy) {
|
|
828
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
829
|
+
if (!acc.includes(action.contentType)) {
|
|
830
|
+
acc.push(action.contentType);
|
|
831
|
+
}
|
|
832
|
+
return acc;
|
|
833
|
+
}, []);
|
|
834
|
+
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
|
|
835
|
+
contentTypeUids
|
|
623
836
|
);
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
$null: true
|
|
837
|
+
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
838
|
+
const formattedData = actions.map((action) => {
|
|
839
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
840
|
+
return {
|
|
841
|
+
...action,
|
|
842
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
843
|
+
contentType: {
|
|
844
|
+
displayName,
|
|
845
|
+
mainFieldValue: action.entry[mainField],
|
|
846
|
+
uid: action.contentType
|
|
635
847
|
}
|
|
636
|
-
}
|
|
848
|
+
};
|
|
849
|
+
});
|
|
850
|
+
const groupName = getGroupName(groupBy);
|
|
851
|
+
return _.groupBy(groupName)(formattedData);
|
|
852
|
+
},
|
|
853
|
+
async getLocalesDataForActions() {
|
|
854
|
+
if (!strapi2.plugin("i18n")) {
|
|
855
|
+
return {};
|
|
637
856
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
857
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
858
|
+
return allLocales.reduce((acc, locale) => {
|
|
859
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
860
|
+
return acc;
|
|
861
|
+
}, {});
|
|
862
|
+
},
|
|
863
|
+
async getContentTypesDataForActions(contentTypesUids) {
|
|
864
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
865
|
+
const contentTypesData = {};
|
|
866
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
867
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
868
|
+
uid: contentTypeUid
|
|
869
|
+
});
|
|
870
|
+
contentTypesData[contentTypeUid] = {
|
|
871
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
872
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
return contentTypesData;
|
|
876
|
+
},
|
|
877
|
+
getContentTypeModelsFromActions(actions) {
|
|
878
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
879
|
+
if (!acc.includes(action.contentType)) {
|
|
880
|
+
acc.push(action.contentType);
|
|
881
|
+
}
|
|
882
|
+
return acc;
|
|
883
|
+
}, []);
|
|
884
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
885
|
+
(acc, contentTypeUid) => {
|
|
886
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
887
|
+
return acc;
|
|
888
|
+
},
|
|
889
|
+
{}
|
|
890
|
+
);
|
|
891
|
+
return contentTypeModelsMap;
|
|
892
|
+
},
|
|
893
|
+
async getAllComponents() {
|
|
894
|
+
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
895
|
+
const components = await contentManagerComponentsService.findAllComponents();
|
|
896
|
+
const componentsMap = components.reduce(
|
|
897
|
+
(acc, component) => {
|
|
898
|
+
acc[component.uid] = component;
|
|
899
|
+
return acc;
|
|
900
|
+
},
|
|
901
|
+
{}
|
|
642
902
|
);
|
|
903
|
+
return componentsMap;
|
|
904
|
+
},
|
|
905
|
+
async delete(releaseId) {
|
|
906
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
907
|
+
populate: {
|
|
908
|
+
actions: {
|
|
909
|
+
fields: ["id"]
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
if (!release2) {
|
|
914
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
915
|
+
}
|
|
916
|
+
if (release2.releasedAt) {
|
|
917
|
+
throw new errors.ValidationError("Release already published");
|
|
918
|
+
}
|
|
919
|
+
await strapi2.db.transaction(async () => {
|
|
920
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
921
|
+
where: {
|
|
922
|
+
id: {
|
|
923
|
+
$in: release2.actions.map((action) => action.id)
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
|
928
|
+
});
|
|
929
|
+
if (release2.scheduledAt) {
|
|
930
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
931
|
+
await schedulingService.cancel(release2.id);
|
|
932
|
+
}
|
|
933
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
934
|
+
return release2;
|
|
935
|
+
},
|
|
936
|
+
async publish(releaseId) {
|
|
937
|
+
const {
|
|
938
|
+
release: release2,
|
|
939
|
+
error
|
|
940
|
+
} = await strapi2.db.transaction(async ({ trx }) => {
|
|
941
|
+
const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
|
|
942
|
+
if (!lockedRelease) {
|
|
943
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
944
|
+
}
|
|
945
|
+
if (lockedRelease.releasedAt) {
|
|
946
|
+
throw new errors.ValidationError("Release already published");
|
|
947
|
+
}
|
|
948
|
+
if (lockedRelease.status === "failed") {
|
|
949
|
+
throw new errors.ValidationError("Release failed to publish");
|
|
950
|
+
}
|
|
951
|
+
try {
|
|
952
|
+
strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
|
|
953
|
+
const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
|
|
954
|
+
releaseId
|
|
955
|
+
);
|
|
956
|
+
await strapi2.db.transaction(async () => {
|
|
957
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
958
|
+
await publishSingleTypeAction(uid, action, id);
|
|
959
|
+
}
|
|
960
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
961
|
+
const uid = contentTypeUid;
|
|
962
|
+
await publishCollectionTypeAction(
|
|
963
|
+
uid,
|
|
964
|
+
collectionTypeActions[uid].entriesToPublishIds,
|
|
965
|
+
collectionTypeActions[uid].entriesToUnpublishIds
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
970
|
+
where: {
|
|
971
|
+
id: releaseId
|
|
972
|
+
},
|
|
973
|
+
data: {
|
|
974
|
+
status: "done",
|
|
975
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
979
|
+
isPublished: true,
|
|
980
|
+
release: release22
|
|
981
|
+
});
|
|
982
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
983
|
+
return { release: release22, error: null };
|
|
984
|
+
} catch (error2) {
|
|
985
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
986
|
+
isPublished: false,
|
|
987
|
+
error: error2
|
|
988
|
+
});
|
|
989
|
+
await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
|
|
990
|
+
status: "failed"
|
|
991
|
+
}).transacting(trx).execute();
|
|
992
|
+
return {
|
|
993
|
+
release: null,
|
|
994
|
+
error: error2
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
if (error) {
|
|
999
|
+
throw error;
|
|
1000
|
+
}
|
|
1001
|
+
return release2;
|
|
1002
|
+
},
|
|
1003
|
+
async updateAction(actionId, releaseId, update) {
|
|
1004
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1005
|
+
where: {
|
|
1006
|
+
id: actionId,
|
|
1007
|
+
release: {
|
|
1008
|
+
id: releaseId,
|
|
1009
|
+
releasedAt: {
|
|
1010
|
+
$null: true
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
},
|
|
1014
|
+
data: update
|
|
1015
|
+
});
|
|
1016
|
+
if (!updatedAction) {
|
|
1017
|
+
throw new errors.NotFoundError(
|
|
1018
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
return updatedAction;
|
|
1022
|
+
},
|
|
1023
|
+
async deleteAction(actionId, releaseId) {
|
|
1024
|
+
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
1025
|
+
where: {
|
|
1026
|
+
id: actionId,
|
|
1027
|
+
release: {
|
|
1028
|
+
id: releaseId,
|
|
1029
|
+
releasedAt: {
|
|
1030
|
+
$null: true
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
if (!deletedAction) {
|
|
1036
|
+
throw new errors.NotFoundError(
|
|
1037
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
this.updateReleaseStatus(releaseId);
|
|
1041
|
+
return deletedAction;
|
|
1042
|
+
},
|
|
1043
|
+
async updateReleaseStatus(releaseId) {
|
|
1044
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
1045
|
+
this.countActions({
|
|
1046
|
+
filters: {
|
|
1047
|
+
release: releaseId
|
|
1048
|
+
}
|
|
1049
|
+
}),
|
|
1050
|
+
this.countActions({
|
|
1051
|
+
filters: {
|
|
1052
|
+
release: releaseId,
|
|
1053
|
+
isEntryValid: false
|
|
1054
|
+
}
|
|
1055
|
+
})
|
|
1056
|
+
]);
|
|
1057
|
+
if (totalActions > 0) {
|
|
1058
|
+
if (invalidActions > 0) {
|
|
1059
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1060
|
+
where: {
|
|
1061
|
+
id: releaseId
|
|
1062
|
+
},
|
|
1063
|
+
data: {
|
|
1064
|
+
status: "blocked"
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1069
|
+
where: {
|
|
1070
|
+
id: releaseId
|
|
1071
|
+
},
|
|
1072
|
+
data: {
|
|
1073
|
+
status: "ready"
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1078
|
+
where: {
|
|
1079
|
+
id: releaseId
|
|
1080
|
+
},
|
|
1081
|
+
data: {
|
|
1082
|
+
status: "empty"
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
643
1085
|
}
|
|
644
|
-
|
|
1086
|
+
};
|
|
1087
|
+
};
|
|
1088
|
+
class AlreadyOnReleaseError extends errors.ApplicationError {
|
|
1089
|
+
constructor(message) {
|
|
1090
|
+
super(message);
|
|
1091
|
+
this.name = "AlreadyOnReleaseError";
|
|
645
1092
|
}
|
|
646
|
-
}
|
|
1093
|
+
}
|
|
647
1094
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
648
1095
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
649
1096
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
@@ -656,7 +1103,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
656
1103
|
(action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
|
|
657
1104
|
);
|
|
658
1105
|
if (isEntryInRelease) {
|
|
659
|
-
throw new
|
|
1106
|
+
throw new AlreadyOnReleaseError(
|
|
660
1107
|
`Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
|
|
661
1108
|
);
|
|
662
1109
|
}
|
|
@@ -688,27 +1135,103 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
688
1135
|
throw new errors.ValidationError("You have reached the maximum number of pending releases");
|
|
689
1136
|
}
|
|
690
1137
|
},
|
|
691
|
-
async validateUniqueNameForPendingRelease(name) {
|
|
1138
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
692
1139
|
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
693
1140
|
filters: {
|
|
694
1141
|
releasedAt: {
|
|
695
1142
|
$null: true
|
|
696
1143
|
},
|
|
697
|
-
name
|
|
1144
|
+
name,
|
|
1145
|
+
...id && { id: { $ne: id } }
|
|
698
1146
|
}
|
|
699
1147
|
});
|
|
700
1148
|
const isNameUnique = pendingReleases.length === 0;
|
|
701
1149
|
if (!isNameUnique) {
|
|
702
1150
|
throw new errors.ValidationError(`Release with name ${name} already exists`);
|
|
703
1151
|
}
|
|
1152
|
+
},
|
|
1153
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
1154
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
1155
|
+
throw new errors.ValidationError("Scheduled at must be later than now");
|
|
1156
|
+
}
|
|
704
1157
|
}
|
|
705
1158
|
});
|
|
1159
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
1160
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
1161
|
+
return {
|
|
1162
|
+
async set(releaseId, scheduleDate) {
|
|
1163
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
1164
|
+
if (!release2) {
|
|
1165
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1166
|
+
}
|
|
1167
|
+
const job = scheduleJob(scheduleDate, async () => {
|
|
1168
|
+
try {
|
|
1169
|
+
await getService("release").publish(releaseId);
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
}
|
|
1172
|
+
this.cancel(releaseId);
|
|
1173
|
+
});
|
|
1174
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1175
|
+
this.cancel(releaseId);
|
|
1176
|
+
}
|
|
1177
|
+
scheduledJobs.set(releaseId, job);
|
|
1178
|
+
return scheduledJobs;
|
|
1179
|
+
},
|
|
1180
|
+
cancel(releaseId) {
|
|
1181
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1182
|
+
scheduledJobs.get(releaseId).cancel();
|
|
1183
|
+
scheduledJobs.delete(releaseId);
|
|
1184
|
+
}
|
|
1185
|
+
return scheduledJobs;
|
|
1186
|
+
},
|
|
1187
|
+
getAll() {
|
|
1188
|
+
return scheduledJobs;
|
|
1189
|
+
},
|
|
1190
|
+
/**
|
|
1191
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
1192
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
1193
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
1194
|
+
*/
|
|
1195
|
+
async syncFromDatabase() {
|
|
1196
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1197
|
+
where: {
|
|
1198
|
+
scheduledAt: {
|
|
1199
|
+
$gte: /* @__PURE__ */ new Date()
|
|
1200
|
+
},
|
|
1201
|
+
releasedAt: null
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
for (const release2 of releases) {
|
|
1205
|
+
this.set(release2.id, release2.scheduledAt);
|
|
1206
|
+
}
|
|
1207
|
+
return scheduledJobs;
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
};
|
|
706
1211
|
const services = {
|
|
707
1212
|
release: createReleaseService,
|
|
708
|
-
"release-validation": createReleaseValidationService
|
|
1213
|
+
"release-validation": createReleaseValidationService,
|
|
1214
|
+
scheduling: createSchedulingService
|
|
709
1215
|
};
|
|
710
1216
|
const RELEASE_SCHEMA = yup.object().shape({
|
|
711
|
-
name: yup.string().trim().required()
|
|
1217
|
+
name: yup.string().trim().required(),
|
|
1218
|
+
scheduledAt: yup.string().nullable(),
|
|
1219
|
+
isScheduled: yup.boolean().optional(),
|
|
1220
|
+
time: yup.string().when("isScheduled", {
|
|
1221
|
+
is: true,
|
|
1222
|
+
then: yup.string().trim().required(),
|
|
1223
|
+
otherwise: yup.string().nullable()
|
|
1224
|
+
}),
|
|
1225
|
+
timezone: yup.string().when("isScheduled", {
|
|
1226
|
+
is: true,
|
|
1227
|
+
then: yup.string().required().nullable(),
|
|
1228
|
+
otherwise: yup.string().nullable()
|
|
1229
|
+
}),
|
|
1230
|
+
date: yup.string().when("isScheduled", {
|
|
1231
|
+
is: true,
|
|
1232
|
+
then: yup.string().required().nullable(),
|
|
1233
|
+
otherwise: yup.string().nullable()
|
|
1234
|
+
})
|
|
712
1235
|
}).required().noUnknown();
|
|
713
1236
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
714
1237
|
const releaseController = {
|
|
@@ -741,26 +1264,30 @@ const releaseController = {
|
|
|
741
1264
|
}
|
|
742
1265
|
};
|
|
743
1266
|
});
|
|
744
|
-
|
|
1267
|
+
const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
|
|
1268
|
+
where: {
|
|
1269
|
+
releasedAt: null
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
745
1273
|
}
|
|
746
1274
|
},
|
|
747
1275
|
async findOne(ctx) {
|
|
748
1276
|
const id = ctx.params.id;
|
|
749
1277
|
const releaseService = getService("release", { strapi });
|
|
750
1278
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
});
|
|
755
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
1279
|
+
if (!release2) {
|
|
1280
|
+
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
1281
|
+
}
|
|
756
1282
|
const count = await releaseService.countActions({
|
|
757
1283
|
filters: {
|
|
758
1284
|
release: id
|
|
759
1285
|
}
|
|
760
1286
|
});
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
1287
|
+
const sanitizedRelease = {
|
|
1288
|
+
...release2,
|
|
1289
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
1290
|
+
};
|
|
764
1291
|
const data = {
|
|
765
1292
|
...sanitizedRelease,
|
|
766
1293
|
actions: {
|
|
@@ -813,8 +1340,27 @@ const releaseController = {
|
|
|
813
1340
|
const id = ctx.params.id;
|
|
814
1341
|
const releaseService = getService("release", { strapi });
|
|
815
1342
|
const release2 = await releaseService.publish(id, { user });
|
|
1343
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1344
|
+
releaseService.countActions({
|
|
1345
|
+
filters: {
|
|
1346
|
+
release: id,
|
|
1347
|
+
type: "publish"
|
|
1348
|
+
}
|
|
1349
|
+
}),
|
|
1350
|
+
releaseService.countActions({
|
|
1351
|
+
filters: {
|
|
1352
|
+
release: id,
|
|
1353
|
+
type: "unpublish"
|
|
1354
|
+
}
|
|
1355
|
+
})
|
|
1356
|
+
]);
|
|
816
1357
|
ctx.body = {
|
|
817
|
-
data: release2
|
|
1358
|
+
data: release2,
|
|
1359
|
+
meta: {
|
|
1360
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1361
|
+
totalPublishedEntries: countPublishActions,
|
|
1362
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1363
|
+
}
|
|
818
1364
|
};
|
|
819
1365
|
}
|
|
820
1366
|
};
|
|
@@ -841,6 +1387,38 @@ const releaseActionController = {
|
|
|
841
1387
|
data: releaseAction2
|
|
842
1388
|
};
|
|
843
1389
|
},
|
|
1390
|
+
async createMany(ctx) {
|
|
1391
|
+
const releaseId = ctx.params.releaseId;
|
|
1392
|
+
const releaseActionsArgs = ctx.request.body;
|
|
1393
|
+
await Promise.all(
|
|
1394
|
+
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
|
1395
|
+
);
|
|
1396
|
+
const releaseService = getService("release", { strapi });
|
|
1397
|
+
const releaseActions = await strapi.db.transaction(async () => {
|
|
1398
|
+
const releaseActions2 = await Promise.all(
|
|
1399
|
+
releaseActionsArgs.map(async (releaseActionArgs) => {
|
|
1400
|
+
try {
|
|
1401
|
+
const action = await releaseService.createAction(releaseId, releaseActionArgs);
|
|
1402
|
+
return action;
|
|
1403
|
+
} catch (error) {
|
|
1404
|
+
if (error instanceof AlreadyOnReleaseError) {
|
|
1405
|
+
return null;
|
|
1406
|
+
}
|
|
1407
|
+
throw error;
|
|
1408
|
+
}
|
|
1409
|
+
})
|
|
1410
|
+
);
|
|
1411
|
+
return releaseActions2;
|
|
1412
|
+
});
|
|
1413
|
+
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
|
1414
|
+
ctx.body = {
|
|
1415
|
+
data: newReleaseActions,
|
|
1416
|
+
meta: {
|
|
1417
|
+
entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
|
|
1418
|
+
totalEntries: releaseActions.length
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
},
|
|
844
1422
|
async findMany(ctx) {
|
|
845
1423
|
const releaseId = ctx.params.releaseId;
|
|
846
1424
|
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
|
@@ -1026,6 +1604,22 @@ const releaseAction = {
|
|
|
1026
1604
|
]
|
|
1027
1605
|
}
|
|
1028
1606
|
},
|
|
1607
|
+
{
|
|
1608
|
+
method: "POST",
|
|
1609
|
+
path: "/:releaseId/actions/bulk",
|
|
1610
|
+
handler: "release-action.createMany",
|
|
1611
|
+
config: {
|
|
1612
|
+
policies: [
|
|
1613
|
+
"admin::isAuthenticatedAdmin",
|
|
1614
|
+
{
|
|
1615
|
+
name: "admin::hasPermissions",
|
|
1616
|
+
config: {
|
|
1617
|
+
actions: ["plugin::content-releases.create-action"]
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
]
|
|
1621
|
+
}
|
|
1622
|
+
},
|
|
1029
1623
|
{
|
|
1030
1624
|
method: "GET",
|
|
1031
1625
|
path: "/:releaseId/actions",
|
|
@@ -1086,6 +1680,7 @@ const getPlugin = () => {
|
|
|
1086
1680
|
return {
|
|
1087
1681
|
register,
|
|
1088
1682
|
bootstrap,
|
|
1683
|
+
destroy,
|
|
1089
1684
|
contentTypes,
|
|
1090
1685
|
services,
|
|
1091
1686
|
controllers,
|
|
@@ -1093,6 +1688,9 @@ const getPlugin = () => {
|
|
|
1093
1688
|
};
|
|
1094
1689
|
}
|
|
1095
1690
|
return {
|
|
1691
|
+
// Always return register, it handles its own feature check
|
|
1692
|
+
register,
|
|
1693
|
+
// Always return contentTypes to avoid losing data when the feature is disabled
|
|
1096
1694
|
contentTypes
|
|
1097
1695
|
};
|
|
1098
1696
|
};
|