@strapi/content-releases 0.0.0-experimental.b5b7b8260a4549f3bd7443fbd68be5ccc9857cd7 → 0.0.0-experimental.c5354c231bf2abb4fe353d2ab7812bced4a1c6fa
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-AYV8n9jv.js → App-OP70yd5M.js} +784 -435
- package/dist/_chunks/App-OP70yd5M.js.map +1 -0
- package/dist/_chunks/App-x6Tjj3HN.mjs +1315 -0
- package/dist/_chunks/App-x6Tjj3HN.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-NFIckO1N.mjs → index-1ejXLtzt.mjs} +367 -48
- package/dist/_chunks/index-1ejXLtzt.mjs.map +1 -0
- package/dist/_chunks/{index-FYDbIuRi.js → index-ydocdaZ0.js} +355 -36
- package/dist/_chunks/index-ydocdaZ0.js.map +1 -0
- package/dist/admin/index.js +2 -2
- package/dist/admin/index.mjs +3 -3
- package/dist/server/index.js +1059 -377
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1060 -379
- package/dist/server/index.mjs.map +1 -1
- package/package.json +16 -11
- package/dist/_chunks/App-AYV8n9jv.js.map +0 -1
- package/dist/_chunks/App-KBaxZVmD.mjs +0 -967
- package/dist/_chunks/App-KBaxZVmD.mjs.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-FYDbIuRi.js.map +0 -1
- package/dist/_chunks/index-NFIckO1N.mjs.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const utils = require("@strapi/utils");
|
|
3
|
+
const isEqual = require("lodash/isEqual");
|
|
4
|
+
const lodash = require("lodash");
|
|
3
5
|
const _ = require("lodash/fp");
|
|
4
6
|
const EE = require("@strapi/strapi/dist/utils/ee");
|
|
7
|
+
const nodeSchedule = require("node-schedule");
|
|
5
8
|
const yup = require("yup");
|
|
6
9
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
7
10
|
function _interopNamespace(e) {
|
|
@@ -22,6 +25,7 @@ function _interopNamespace(e) {
|
|
|
22
25
|
n.default = e;
|
|
23
26
|
return Object.freeze(n);
|
|
24
27
|
}
|
|
28
|
+
const isEqual__default = /* @__PURE__ */ _interopDefault(isEqual);
|
|
25
29
|
const ___default = /* @__PURE__ */ _interopDefault(_);
|
|
26
30
|
const EE__default = /* @__PURE__ */ _interopDefault(EE);
|
|
27
31
|
const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
|
|
@@ -71,47 +75,248 @@ const ACTIONS = [
|
|
|
71
75
|
pluginName: "content-releases"
|
|
72
76
|
}
|
|
73
77
|
];
|
|
78
|
+
const ALLOWED_WEBHOOK_EVENTS = {
|
|
79
|
+
RELEASES_PUBLISH: "releases.publish"
|
|
80
|
+
};
|
|
74
81
|
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
75
82
|
return strapi2.plugin("content-releases").service(name);
|
|
76
83
|
};
|
|
77
|
-
const {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
};
|
|
104
|
+
async function deleteActionsOnDisableDraftAndPublish({
|
|
105
|
+
oldContentTypes,
|
|
106
|
+
contentTypes: contentTypes2
|
|
107
|
+
}) {
|
|
108
|
+
if (!oldContentTypes) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
for (const uid in contentTypes2) {
|
|
112
|
+
if (!oldContentTypes[uid]) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const oldContentType = oldContentTypes[uid];
|
|
116
|
+
const contentType = contentTypes2[uid];
|
|
117
|
+
if (utils.contentTypes.hasDraftAndPublish(oldContentType) && !utils.contentTypes.hasDraftAndPublish(contentType)) {
|
|
118
|
+
await strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: uid }).execute();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
123
|
+
const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
|
|
124
|
+
if (deletedContentTypes.length) {
|
|
125
|
+
await utils.mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
|
|
126
|
+
return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
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
|
|
88
140
|
}
|
|
89
141
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
96
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
|
|
97
190
|
);
|
|
98
|
-
|
|
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
|
+
for (const uid in contentTypes2) {
|
|
239
|
+
if (!oldContentTypes[uid]) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const oldContentType = oldContentTypes[uid];
|
|
243
|
+
const contentType = contentTypes2[uid];
|
|
244
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
245
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
246
|
+
if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
|
|
247
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
248
|
+
locale: null
|
|
249
|
+
}).where({ contentType: uid }).execute();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
254
|
+
if (!oldContentTypes) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
for (const uid in contentTypes2) {
|
|
258
|
+
if (!oldContentTypes[uid]) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const oldContentType = oldContentTypes[uid];
|
|
262
|
+
const contentType = contentTypes2[uid];
|
|
263
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
264
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
265
|
+
const { getDefaultLocale } = i18nPlugin.service("locales");
|
|
266
|
+
if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
|
|
267
|
+
const defaultLocale = await getDefaultLocale();
|
|
268
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
269
|
+
locale: defaultLocale
|
|
270
|
+
}).where({ contentType: uid }).execute();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
|
|
275
|
+
const register = async ({ strapi: strapi2 }) => {
|
|
276
|
+
if (features$2.isEnabled("cms-content-releases")) {
|
|
277
|
+
await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
|
278
|
+
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish).register(disableContentTypeLocalized);
|
|
279
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
280
|
+
}
|
|
281
|
+
if (strapi2.plugin("graphql")) {
|
|
282
|
+
const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
|
|
283
|
+
graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
|
|
284
|
+
graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
|
|
99
285
|
}
|
|
100
286
|
};
|
|
101
287
|
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
102
288
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
103
|
-
if (features$1.isEnabled("cms-content-releases")
|
|
289
|
+
if (features$1.isEnabled("cms-content-releases")) {
|
|
290
|
+
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
291
|
+
(uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
|
|
292
|
+
);
|
|
104
293
|
strapi2.db.lifecycles.subscribe({
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
294
|
+
models: contentTypesWithDraftAndPublish,
|
|
295
|
+
async afterDelete(event) {
|
|
296
|
+
try {
|
|
297
|
+
const { model, result } = event;
|
|
298
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
299
|
+
const { id } = result;
|
|
300
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
301
|
+
where: {
|
|
302
|
+
actions: {
|
|
303
|
+
target_type: model.uid,
|
|
304
|
+
target_id: id
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
309
|
+
where: {
|
|
310
|
+
target_type: model.uid,
|
|
311
|
+
target_id: id
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
for (const release2 of releases) {
|
|
315
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
113
316
|
}
|
|
114
|
-
}
|
|
317
|
+
}
|
|
318
|
+
} catch (error) {
|
|
319
|
+
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
115
320
|
}
|
|
116
321
|
},
|
|
117
322
|
/**
|
|
@@ -120,7 +325,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
120
325
|
*/
|
|
121
326
|
async beforeDeleteMany(event) {
|
|
122
327
|
const { model, params } = event;
|
|
123
|
-
if (model.kind === "collectionType" && model.options
|
|
328
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
124
329
|
const { where } = params;
|
|
125
330
|
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
126
331
|
event.state.entriesToDelete = entriesToDelete;
|
|
@@ -131,20 +336,94 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
131
336
|
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
132
337
|
*/
|
|
133
338
|
async afterDeleteMany(event) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
339
|
+
try {
|
|
340
|
+
const { model, state } = event;
|
|
341
|
+
const entriesToDelete = state.entriesToDelete;
|
|
342
|
+
if (entriesToDelete) {
|
|
343
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
344
|
+
where: {
|
|
345
|
+
actions: {
|
|
346
|
+
target_type: model.uid,
|
|
347
|
+
target_id: {
|
|
348
|
+
$in: entriesToDelete.map(
|
|
349
|
+
(entry) => entry.id
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
142
353
|
}
|
|
354
|
+
});
|
|
355
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
356
|
+
where: {
|
|
357
|
+
target_type: model.uid,
|
|
358
|
+
target_id: {
|
|
359
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
for (const release2 of releases) {
|
|
364
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
143
365
|
}
|
|
366
|
+
}
|
|
367
|
+
} catch (error) {
|
|
368
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
369
|
+
error
|
|
144
370
|
});
|
|
145
371
|
}
|
|
372
|
+
},
|
|
373
|
+
async afterUpdate(event) {
|
|
374
|
+
try {
|
|
375
|
+
const { model, result } = event;
|
|
376
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
377
|
+
const isEntryValid = await getEntryValidStatus(
|
|
378
|
+
model.uid,
|
|
379
|
+
result,
|
|
380
|
+
{
|
|
381
|
+
strapi: strapi2
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
385
|
+
where: {
|
|
386
|
+
target_type: model.uid,
|
|
387
|
+
target_id: result.id
|
|
388
|
+
},
|
|
389
|
+
data: {
|
|
390
|
+
isEntryValid
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
394
|
+
where: {
|
|
395
|
+
actions: {
|
|
396
|
+
target_type: model.uid,
|
|
397
|
+
target_id: result.id
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
for (const release2 of releases) {
|
|
402
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
} catch (error) {
|
|
406
|
+
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
407
|
+
}
|
|
146
408
|
}
|
|
147
409
|
});
|
|
410
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
411
|
+
strapi2.log.error(
|
|
412
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
413
|
+
);
|
|
414
|
+
throw err;
|
|
415
|
+
});
|
|
416
|
+
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
417
|
+
strapi2.webhookStore.addAllowedEvent(key, value);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
const destroy = async ({ strapi: strapi2 }) => {
|
|
422
|
+
const scheduledJobs = getService("scheduling", {
|
|
423
|
+
strapi: strapi2
|
|
424
|
+
}).getAll();
|
|
425
|
+
for (const [, job] of scheduledJobs) {
|
|
426
|
+
job.cancel();
|
|
148
427
|
}
|
|
149
428
|
};
|
|
150
429
|
const schema$1 = {
|
|
@@ -173,6 +452,17 @@ const schema$1 = {
|
|
|
173
452
|
releasedAt: {
|
|
174
453
|
type: "datetime"
|
|
175
454
|
},
|
|
455
|
+
scheduledAt: {
|
|
456
|
+
type: "datetime"
|
|
457
|
+
},
|
|
458
|
+
timezone: {
|
|
459
|
+
type: "string"
|
|
460
|
+
},
|
|
461
|
+
status: {
|
|
462
|
+
type: "enumeration",
|
|
463
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
464
|
+
required: true
|
|
465
|
+
},
|
|
176
466
|
actions: {
|
|
177
467
|
type: "relation",
|
|
178
468
|
relation: "oneToMany",
|
|
@@ -225,6 +515,9 @@ const schema = {
|
|
|
225
515
|
relation: "manyToOne",
|
|
226
516
|
target: RELEASE_MODEL_UID,
|
|
227
517
|
inversedBy: "actions"
|
|
518
|
+
},
|
|
519
|
+
isEntryValid: {
|
|
520
|
+
type: "boolean"
|
|
228
521
|
}
|
|
229
522
|
}
|
|
230
523
|
};
|
|
@@ -235,361 +528,593 @@ const contentTypes = {
|
|
|
235
528
|
release: release$1,
|
|
236
529
|
"release-action": releaseAction$1
|
|
237
530
|
};
|
|
238
|
-
const createReleaseActionService = ({ strapi: strapi2 }) => ({
|
|
239
|
-
async deleteManyForContentType(contentTypeUid) {
|
|
240
|
-
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
241
|
-
where: {
|
|
242
|
-
target_type: contentTypeUid
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
531
|
const getGroupName = (queryValue) => {
|
|
248
532
|
switch (queryValue) {
|
|
249
533
|
case "contentType":
|
|
250
|
-
return "
|
|
534
|
+
return "contentType.displayName";
|
|
251
535
|
case "action":
|
|
252
536
|
return "type";
|
|
253
537
|
case "locale":
|
|
254
|
-
return ___default.default.getOr("No locale", "
|
|
538
|
+
return ___default.default.getOr("No locale", "locale.name");
|
|
255
539
|
default:
|
|
256
|
-
return "
|
|
540
|
+
return "contentType.displayName";
|
|
257
541
|
}
|
|
258
542
|
};
|
|
259
|
-
const createReleaseService = ({ strapi: strapi2 }) =>
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
543
|
+
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
544
|
+
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
545
|
+
strapi2.eventHub.emit(event, {
|
|
546
|
+
isPublished,
|
|
547
|
+
error,
|
|
548
|
+
release: release2
|
|
265
549
|
});
|
|
266
|
-
}
|
|
267
|
-
async
|
|
268
|
-
const
|
|
269
|
-
|
|
550
|
+
};
|
|
551
|
+
const publishSingleTypeAction = async (uid, actionType, entryId) => {
|
|
552
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
553
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
554
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
555
|
+
const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
|
|
556
|
+
try {
|
|
557
|
+
if (actionType === "publish") {
|
|
558
|
+
await entityManagerService.publish(entry, uid);
|
|
559
|
+
} else {
|
|
560
|
+
await entityManagerService.unpublish(entry, uid);
|
|
561
|
+
}
|
|
562
|
+
} catch (error) {
|
|
563
|
+
if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
564
|
+
;
|
|
565
|
+
else {
|
|
566
|
+
throw error;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
|
|
571
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
572
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
573
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
574
|
+
const entriesToPublish = await strapi2.entityService.findMany(uid, {
|
|
575
|
+
filters: {
|
|
576
|
+
id: {
|
|
577
|
+
$in: entriesToPublishIds
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
populate
|
|
270
581
|
});
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
582
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
|
|
583
|
+
filters: {
|
|
584
|
+
id: {
|
|
585
|
+
$in: entriestoUnpublishIds
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
populate
|
|
589
|
+
});
|
|
590
|
+
if (entriesToPublish.length > 0) {
|
|
591
|
+
await entityManagerService.publishMany(entriesToPublish, uid);
|
|
592
|
+
}
|
|
593
|
+
if (entriesToUnpublish.length > 0) {
|
|
594
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, uid);
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
const getFormattedActions = async (releaseId) => {
|
|
598
|
+
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
599
|
+
where: {
|
|
600
|
+
release: {
|
|
601
|
+
id: releaseId
|
|
602
|
+
}
|
|
603
|
+
},
|
|
276
604
|
populate: {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
count: true
|
|
605
|
+
entry: {
|
|
606
|
+
fields: ["id"]
|
|
280
607
|
}
|
|
281
608
|
}
|
|
282
609
|
});
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
610
|
+
if (actions.length === 0) {
|
|
611
|
+
throw new utils.errors.ValidationError("No entries to publish");
|
|
612
|
+
}
|
|
613
|
+
const collectionTypeActions = {};
|
|
614
|
+
const singleTypeActions = [];
|
|
615
|
+
for (const action of actions) {
|
|
616
|
+
const contentTypeUid = action.contentType;
|
|
617
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
618
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
619
|
+
collectionTypeActions[contentTypeUid] = {
|
|
620
|
+
entriesToPublishIds: [],
|
|
621
|
+
entriesToUnpublishIds: []
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
if (action.type === "publish") {
|
|
625
|
+
collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
|
|
626
|
+
} else {
|
|
627
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
singleTypeActions.push({
|
|
631
|
+
uid: contentTypeUid,
|
|
632
|
+
action: action.type,
|
|
633
|
+
id: action.entry.id
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return { collectionTypeActions, singleTypeActions };
|
|
638
|
+
};
|
|
639
|
+
return {
|
|
640
|
+
async create(releaseData, { user }) {
|
|
641
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
|
|
642
|
+
const {
|
|
643
|
+
validatePendingReleasesLimit,
|
|
644
|
+
validateUniqueNameForPendingRelease,
|
|
645
|
+
validateScheduledAtIsLaterThanNow
|
|
646
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
647
|
+
await Promise.all([
|
|
648
|
+
validatePendingReleasesLimit(),
|
|
649
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
650
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
651
|
+
]);
|
|
652
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
653
|
+
data: {
|
|
654
|
+
...releaseWithCreatorFields,
|
|
655
|
+
status: "empty"
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
if (releaseWithCreatorFields.scheduledAt) {
|
|
659
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
660
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
294
661
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
662
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
663
|
+
return release2;
|
|
664
|
+
},
|
|
665
|
+
async findOne(id, query = {}) {
|
|
666
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
667
|
+
...query
|
|
668
|
+
});
|
|
669
|
+
return release2;
|
|
670
|
+
},
|
|
671
|
+
findPage(query) {
|
|
672
|
+
return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
|
|
673
|
+
...query,
|
|
674
|
+
populate: {
|
|
675
|
+
actions: {
|
|
676
|
+
// @ts-expect-error Ignore missing properties
|
|
677
|
+
count: true
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
},
|
|
682
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
683
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
684
|
+
where: {
|
|
685
|
+
actions: {
|
|
686
|
+
target_type: contentTypeUid,
|
|
687
|
+
target_id: entryId
|
|
688
|
+
},
|
|
689
|
+
releasedAt: {
|
|
690
|
+
$null: true
|
|
691
|
+
}
|
|
692
|
+
},
|
|
693
|
+
populate: {
|
|
694
|
+
// Filter the action to get only the content type entry
|
|
695
|
+
actions: {
|
|
696
|
+
where: {
|
|
301
697
|
target_type: contentTypeUid,
|
|
302
698
|
target_id: entryId
|
|
303
699
|
}
|
|
304
700
|
}
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
actions: null
|
|
308
701
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
702
|
+
});
|
|
703
|
+
return releases.map((release2) => {
|
|
704
|
+
if (release2.actions?.length) {
|
|
705
|
+
const [actionForEntry] = release2.actions;
|
|
706
|
+
delete release2.actions;
|
|
707
|
+
return {
|
|
708
|
+
...release2,
|
|
709
|
+
action: actionForEntry
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
return release2;
|
|
713
|
+
});
|
|
714
|
+
},
|
|
715
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
716
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
314
717
|
where: {
|
|
315
|
-
|
|
316
|
-
|
|
718
|
+
releasedAt: {
|
|
719
|
+
$null: true
|
|
720
|
+
},
|
|
721
|
+
actions: {
|
|
722
|
+
target_type: contentTypeUid,
|
|
723
|
+
target_id: entryId
|
|
724
|
+
}
|
|
317
725
|
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
726
|
+
});
|
|
727
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
728
|
+
where: {
|
|
729
|
+
$or: [
|
|
730
|
+
{
|
|
731
|
+
id: {
|
|
732
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
733
|
+
}
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
actions: null
|
|
737
|
+
}
|
|
738
|
+
],
|
|
739
|
+
releasedAt: {
|
|
740
|
+
$null: true
|
|
741
|
+
}
|
|
325
742
|
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
|
|
743
|
+
});
|
|
744
|
+
return releases.map((release2) => {
|
|
745
|
+
if (release2.actions?.length) {
|
|
746
|
+
const [actionForEntry] = release2.actions;
|
|
747
|
+
delete release2.actions;
|
|
748
|
+
return {
|
|
749
|
+
...release2,
|
|
750
|
+
action: actionForEntry
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
return release2;
|
|
754
|
+
});
|
|
755
|
+
},
|
|
756
|
+
async update(id, releaseData, { user }) {
|
|
757
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(
|
|
758
|
+
releaseData
|
|
759
|
+
);
|
|
760
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
761
|
+
"release-validation",
|
|
762
|
+
{ strapi: strapi2 }
|
|
763
|
+
);
|
|
764
|
+
await Promise.all([
|
|
765
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
766
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
767
|
+
]);
|
|
768
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
769
|
+
if (!release2) {
|
|
770
|
+
throw new utils.errors.NotFoundError(`No release found for id ${id}`);
|
|
329
771
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (release2.actions?.length) {
|
|
333
|
-
const [actionForEntry] = release2.actions;
|
|
334
|
-
delete release2.actions;
|
|
335
|
-
return {
|
|
336
|
-
...release2,
|
|
337
|
-
action: actionForEntry
|
|
338
|
-
};
|
|
772
|
+
if (release2.releasedAt) {
|
|
773
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
339
774
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
/*
|
|
354
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
355
|
-
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
356
|
-
*/
|
|
357
|
-
// @ts-expect-error see above
|
|
358
|
-
data: releaseWithCreatorFields
|
|
359
|
-
});
|
|
360
|
-
return updatedRelease;
|
|
361
|
-
},
|
|
362
|
-
async createAction(releaseId, action) {
|
|
363
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
364
|
-
strapi: strapi2
|
|
365
|
-
});
|
|
366
|
-
await Promise.all([
|
|
367
|
-
validateEntryContentType(action.entry.contentType),
|
|
368
|
-
validateUniqueEntry(releaseId, action)
|
|
369
|
-
]);
|
|
370
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
371
|
-
if (!release2) {
|
|
372
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
373
|
-
}
|
|
374
|
-
if (release2.releasedAt) {
|
|
375
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
376
|
-
}
|
|
377
|
-
const { entry, type } = action;
|
|
378
|
-
return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
379
|
-
data: {
|
|
380
|
-
type,
|
|
381
|
-
contentType: entry.contentType,
|
|
382
|
-
locale: entry.locale,
|
|
383
|
-
entry: {
|
|
384
|
-
id: entry.id,
|
|
385
|
-
__type: entry.contentType,
|
|
386
|
-
__pivot: { field: "entry" }
|
|
387
|
-
},
|
|
388
|
-
release: releaseId
|
|
389
|
-
},
|
|
390
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
391
|
-
});
|
|
392
|
-
},
|
|
393
|
-
async findActions(releaseId, query) {
|
|
394
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
395
|
-
fields: ["id"]
|
|
396
|
-
});
|
|
397
|
-
if (!release2) {
|
|
398
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
399
|
-
}
|
|
400
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
401
|
-
...query,
|
|
402
|
-
populate: {
|
|
403
|
-
entry: true
|
|
404
|
-
},
|
|
405
|
-
filters: {
|
|
406
|
-
release: releaseId
|
|
775
|
+
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
776
|
+
/*
|
|
777
|
+
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
778
|
+
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
779
|
+
*/
|
|
780
|
+
// @ts-expect-error see above
|
|
781
|
+
data: releaseWithCreatorFields
|
|
782
|
+
});
|
|
783
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
784
|
+
if (releaseData.scheduledAt) {
|
|
785
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
786
|
+
} else if (release2.scheduledAt) {
|
|
787
|
+
schedulingService.cancel(id);
|
|
407
788
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
789
|
+
this.updateReleaseStatus(id);
|
|
790
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
791
|
+
return updatedRelease;
|
|
792
|
+
},
|
|
793
|
+
async createAction(releaseId, action) {
|
|
794
|
+
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
795
|
+
strapi: strapi2
|
|
796
|
+
});
|
|
797
|
+
await Promise.all([
|
|
798
|
+
validateEntryContentType(action.entry.contentType),
|
|
799
|
+
validateUniqueEntry(releaseId, action)
|
|
800
|
+
]);
|
|
801
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
802
|
+
if (!release2) {
|
|
803
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
417
804
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
805
|
+
if (release2.releasedAt) {
|
|
806
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
807
|
+
}
|
|
808
|
+
const { entry, type } = action;
|
|
809
|
+
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
810
|
+
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
811
|
+
const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
812
|
+
data: {
|
|
813
|
+
type,
|
|
814
|
+
contentType: entry.contentType,
|
|
815
|
+
locale: entry.locale,
|
|
816
|
+
isEntryValid,
|
|
817
|
+
entry: {
|
|
818
|
+
id: entry.id,
|
|
819
|
+
__type: entry.contentType,
|
|
820
|
+
__pivot: { field: "entry" }
|
|
433
821
|
},
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
|
-
});
|
|
439
|
-
const groupName = getGroupName(groupBy);
|
|
440
|
-
return ___default.default.groupBy(groupName)(formattedData);
|
|
441
|
-
},
|
|
442
|
-
async getLocalesDataForActions() {
|
|
443
|
-
if (!strapi2.plugin("i18n")) {
|
|
444
|
-
return {};
|
|
445
|
-
}
|
|
446
|
-
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
447
|
-
return allLocales.reduce((acc, locale) => {
|
|
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
|
|
822
|
+
release: releaseId
|
|
823
|
+
},
|
|
824
|
+
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
458
825
|
});
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
populate: {
|
|
469
|
-
actions: {
|
|
470
|
-
fields: ["id"]
|
|
471
|
-
}
|
|
826
|
+
this.updateReleaseStatus(releaseId);
|
|
827
|
+
return releaseAction2;
|
|
828
|
+
},
|
|
829
|
+
async findActions(releaseId, query) {
|
|
830
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
831
|
+
fields: ["id"]
|
|
832
|
+
});
|
|
833
|
+
if (!release2) {
|
|
834
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
472
835
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
479
|
-
}
|
|
480
|
-
await strapi2.db.transaction(async () => {
|
|
481
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
482
|
-
where: {
|
|
483
|
-
id: {
|
|
484
|
-
$in: release2.actions.map((action) => action.id)
|
|
836
|
+
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
837
|
+
...query,
|
|
838
|
+
populate: {
|
|
839
|
+
entry: {
|
|
840
|
+
populate: "*"
|
|
485
841
|
}
|
|
842
|
+
},
|
|
843
|
+
filters: {
|
|
844
|
+
release: releaseId
|
|
486
845
|
}
|
|
487
846
|
});
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
847
|
+
},
|
|
848
|
+
async countActions(query) {
|
|
849
|
+
return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
|
|
850
|
+
},
|
|
851
|
+
async groupActions(actions, groupBy) {
|
|
852
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
853
|
+
if (!acc.includes(action.contentType)) {
|
|
854
|
+
acc.push(action.contentType);
|
|
855
|
+
}
|
|
856
|
+
return acc;
|
|
857
|
+
}, []);
|
|
858
|
+
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
|
|
859
|
+
contentTypeUids
|
|
860
|
+
);
|
|
861
|
+
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
862
|
+
const formattedData = actions.map((action) => {
|
|
863
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
864
|
+
return {
|
|
865
|
+
...action,
|
|
866
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
867
|
+
contentType: {
|
|
868
|
+
displayName,
|
|
869
|
+
mainFieldValue: action.entry[mainField],
|
|
870
|
+
uid: action.contentType
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
});
|
|
874
|
+
const groupName = getGroupName(groupBy);
|
|
875
|
+
return ___default.default.groupBy(groupName)(formattedData);
|
|
876
|
+
},
|
|
877
|
+
async getLocalesDataForActions() {
|
|
878
|
+
if (!strapi2.plugin("i18n")) {
|
|
879
|
+
return {};
|
|
880
|
+
}
|
|
881
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
882
|
+
return allLocales.reduce((acc, locale) => {
|
|
883
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
884
|
+
return acc;
|
|
885
|
+
}, {});
|
|
886
|
+
},
|
|
887
|
+
async getContentTypesDataForActions(contentTypesUids) {
|
|
888
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
889
|
+
const contentTypesData = {};
|
|
890
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
891
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
892
|
+
uid: contentTypeUid
|
|
893
|
+
});
|
|
894
|
+
contentTypesData[contentTypeUid] = {
|
|
895
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
896
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
return contentTypesData;
|
|
900
|
+
},
|
|
901
|
+
getContentTypeModelsFromActions(actions) {
|
|
902
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
903
|
+
if (!acc.includes(action.contentType)) {
|
|
904
|
+
acc.push(action.contentType);
|
|
905
|
+
}
|
|
906
|
+
return acc;
|
|
907
|
+
}, []);
|
|
908
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
909
|
+
(acc, contentTypeUid) => {
|
|
910
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
911
|
+
return acc;
|
|
912
|
+
},
|
|
913
|
+
{}
|
|
914
|
+
);
|
|
915
|
+
return contentTypeModelsMap;
|
|
916
|
+
},
|
|
917
|
+
async getAllComponents() {
|
|
918
|
+
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
919
|
+
const components = await contentManagerComponentsService.findAllComponents();
|
|
920
|
+
const componentsMap = components.reduce(
|
|
921
|
+
(acc, component) => {
|
|
922
|
+
acc[component.uid] = component;
|
|
923
|
+
return acc;
|
|
924
|
+
},
|
|
925
|
+
{}
|
|
926
|
+
);
|
|
927
|
+
return componentsMap;
|
|
928
|
+
},
|
|
929
|
+
async delete(releaseId) {
|
|
930
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
497
931
|
populate: {
|
|
498
932
|
actions: {
|
|
499
|
-
|
|
500
|
-
entry: true
|
|
501
|
-
}
|
|
933
|
+
fields: ["id"]
|
|
502
934
|
}
|
|
503
935
|
}
|
|
936
|
+
});
|
|
937
|
+
if (!release2) {
|
|
938
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
504
939
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
508
|
-
}
|
|
509
|
-
if (releaseWithPopulatedActionEntries.releasedAt) {
|
|
510
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
511
|
-
}
|
|
512
|
-
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
513
|
-
throw new utils.errors.ValidationError("No entries to publish");
|
|
514
|
-
}
|
|
515
|
-
const actions = {};
|
|
516
|
-
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
517
|
-
const contentTypeUid = action.contentType;
|
|
518
|
-
if (!actions[contentTypeUid]) {
|
|
519
|
-
actions[contentTypeUid] = {
|
|
520
|
-
publish: [],
|
|
521
|
-
unpublish: []
|
|
522
|
-
};
|
|
940
|
+
if (release2.releasedAt) {
|
|
941
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
523
942
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
943
|
+
await strapi2.db.transaction(async () => {
|
|
944
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
945
|
+
where: {
|
|
946
|
+
id: {
|
|
947
|
+
$in: release2.actions.map((action) => action.id)
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
|
952
|
+
});
|
|
953
|
+
if (release2.scheduledAt) {
|
|
954
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
955
|
+
await schedulingService.cancel(release2.id);
|
|
528
956
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
957
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
958
|
+
return release2;
|
|
959
|
+
},
|
|
960
|
+
async publish(releaseId) {
|
|
961
|
+
const {
|
|
962
|
+
release: release2,
|
|
963
|
+
error
|
|
964
|
+
} = await strapi2.db.transaction(async ({ trx }) => {
|
|
965
|
+
const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
|
|
966
|
+
if (!lockedRelease) {
|
|
967
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
968
|
+
}
|
|
969
|
+
if (lockedRelease.releasedAt) {
|
|
970
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
536
971
|
}
|
|
537
|
-
if (
|
|
538
|
-
|
|
972
|
+
if (lockedRelease.status === "failed") {
|
|
973
|
+
throw new utils.errors.ValidationError("Release failed to publish");
|
|
974
|
+
}
|
|
975
|
+
try {
|
|
976
|
+
strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
|
|
977
|
+
const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
|
|
978
|
+
releaseId
|
|
979
|
+
);
|
|
980
|
+
await strapi2.db.transaction(async () => {
|
|
981
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
982
|
+
await publishSingleTypeAction(uid, action, id);
|
|
983
|
+
}
|
|
984
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
985
|
+
const uid = contentTypeUid;
|
|
986
|
+
await publishCollectionTypeAction(
|
|
987
|
+
uid,
|
|
988
|
+
collectionTypeActions[uid].entriesToPublishIds,
|
|
989
|
+
collectionTypeActions[uid].entriesToUnpublishIds
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
994
|
+
where: {
|
|
995
|
+
id: releaseId
|
|
996
|
+
},
|
|
997
|
+
data: {
|
|
998
|
+
status: "done",
|
|
999
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
1003
|
+
isPublished: true,
|
|
1004
|
+
release: release22
|
|
1005
|
+
});
|
|
1006
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
1007
|
+
return { release: release22, error: null };
|
|
1008
|
+
} catch (error2) {
|
|
1009
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
1010
|
+
isPublished: false,
|
|
1011
|
+
error: error2
|
|
1012
|
+
});
|
|
1013
|
+
await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
|
|
1014
|
+
status: "failed"
|
|
1015
|
+
}).transacting(trx).execute();
|
|
1016
|
+
return {
|
|
1017
|
+
release: null,
|
|
1018
|
+
error: error2
|
|
1019
|
+
};
|
|
539
1020
|
}
|
|
1021
|
+
});
|
|
1022
|
+
if (error) {
|
|
1023
|
+
throw error;
|
|
540
1024
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
1025
|
+
return release2;
|
|
1026
|
+
},
|
|
1027
|
+
async updateAction(actionId, releaseId, update) {
|
|
1028
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1029
|
+
where: {
|
|
1030
|
+
id: actionId,
|
|
1031
|
+
release: {
|
|
1032
|
+
id: releaseId,
|
|
1033
|
+
releasedAt: {
|
|
1034
|
+
$null: true
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
},
|
|
1038
|
+
data: update
|
|
1039
|
+
});
|
|
1040
|
+
if (!updatedAction) {
|
|
1041
|
+
throw new utils.errors.NotFoundError(
|
|
1042
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1043
|
+
);
|
|
549
1044
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
1045
|
+
return updatedAction;
|
|
1046
|
+
},
|
|
1047
|
+
async deleteAction(actionId, releaseId) {
|
|
1048
|
+
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
1049
|
+
where: {
|
|
1050
|
+
id: actionId,
|
|
1051
|
+
release: {
|
|
1052
|
+
id: releaseId,
|
|
1053
|
+
releasedAt: {
|
|
1054
|
+
$null: true
|
|
1055
|
+
}
|
|
561
1056
|
}
|
|
562
1057
|
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
);
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
release: {
|
|
578
|
-
id: releaseId,
|
|
579
|
-
releasedAt: {
|
|
580
|
-
$null: true
|
|
1058
|
+
});
|
|
1059
|
+
if (!deletedAction) {
|
|
1060
|
+
throw new utils.errors.NotFoundError(
|
|
1061
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
this.updateReleaseStatus(releaseId);
|
|
1065
|
+
return deletedAction;
|
|
1066
|
+
},
|
|
1067
|
+
async updateReleaseStatus(releaseId) {
|
|
1068
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
1069
|
+
this.countActions({
|
|
1070
|
+
filters: {
|
|
1071
|
+
release: releaseId
|
|
581
1072
|
}
|
|
1073
|
+
}),
|
|
1074
|
+
this.countActions({
|
|
1075
|
+
filters: {
|
|
1076
|
+
release: releaseId,
|
|
1077
|
+
isEntryValid: false
|
|
1078
|
+
}
|
|
1079
|
+
})
|
|
1080
|
+
]);
|
|
1081
|
+
if (totalActions > 0) {
|
|
1082
|
+
if (invalidActions > 0) {
|
|
1083
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1084
|
+
where: {
|
|
1085
|
+
id: releaseId
|
|
1086
|
+
},
|
|
1087
|
+
data: {
|
|
1088
|
+
status: "blocked"
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
582
1091
|
}
|
|
1092
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1093
|
+
where: {
|
|
1094
|
+
id: releaseId
|
|
1095
|
+
},
|
|
1096
|
+
data: {
|
|
1097
|
+
status: "ready"
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
583
1100
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
1101
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1102
|
+
where: {
|
|
1103
|
+
id: releaseId
|
|
1104
|
+
},
|
|
1105
|
+
data: {
|
|
1106
|
+
status: "empty"
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
589
1109
|
}
|
|
590
|
-
|
|
1110
|
+
};
|
|
1111
|
+
};
|
|
1112
|
+
class AlreadyOnReleaseError extends utils.errors.ApplicationError {
|
|
1113
|
+
constructor(message) {
|
|
1114
|
+
super(message);
|
|
1115
|
+
this.name = "AlreadyOnReleaseError";
|
|
591
1116
|
}
|
|
592
|
-
}
|
|
1117
|
+
}
|
|
593
1118
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
594
1119
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
595
1120
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
@@ -602,7 +1127,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
602
1127
|
(action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
|
|
603
1128
|
);
|
|
604
1129
|
if (isEntryInRelease) {
|
|
605
|
-
throw new
|
|
1130
|
+
throw new AlreadyOnReleaseError(
|
|
606
1131
|
`Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
|
|
607
1132
|
);
|
|
608
1133
|
}
|
|
@@ -633,34 +1158,104 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
633
1158
|
if (pendingReleasesCount >= maximumPendingReleases) {
|
|
634
1159
|
throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
|
|
635
1160
|
}
|
|
1161
|
+
},
|
|
1162
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
1163
|
+
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
1164
|
+
filters: {
|
|
1165
|
+
releasedAt: {
|
|
1166
|
+
$null: true
|
|
1167
|
+
},
|
|
1168
|
+
name,
|
|
1169
|
+
...id && { id: { $ne: id } }
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
const isNameUnique = pendingReleases.length === 0;
|
|
1173
|
+
if (!isNameUnique) {
|
|
1174
|
+
throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
|
|
1175
|
+
}
|
|
1176
|
+
},
|
|
1177
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
1178
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
1179
|
+
throw new utils.errors.ValidationError("Scheduled at must be later than now");
|
|
1180
|
+
}
|
|
636
1181
|
}
|
|
637
1182
|
});
|
|
638
|
-
const
|
|
639
|
-
const
|
|
640
|
-
destroyListenerCallbacks: []
|
|
641
|
-
};
|
|
1183
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
1184
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
642
1185
|
return {
|
|
643
|
-
|
|
644
|
-
|
|
1186
|
+
async set(releaseId, scheduleDate) {
|
|
1187
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
1188
|
+
if (!release2) {
|
|
1189
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1190
|
+
}
|
|
1191
|
+
const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
|
|
1192
|
+
try {
|
|
1193
|
+
await getService("release").publish(releaseId);
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
}
|
|
1196
|
+
this.cancel(releaseId);
|
|
1197
|
+
});
|
|
1198
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1199
|
+
this.cancel(releaseId);
|
|
1200
|
+
}
|
|
1201
|
+
scheduledJobs.set(releaseId, job);
|
|
1202
|
+
return scheduledJobs;
|
|
645
1203
|
},
|
|
646
|
-
|
|
647
|
-
if (
|
|
648
|
-
|
|
1204
|
+
cancel(releaseId) {
|
|
1205
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1206
|
+
scheduledJobs.get(releaseId).cancel();
|
|
1207
|
+
scheduledJobs.delete(releaseId);
|
|
649
1208
|
}
|
|
650
|
-
|
|
651
|
-
|
|
1209
|
+
return scheduledJobs;
|
|
1210
|
+
},
|
|
1211
|
+
getAll() {
|
|
1212
|
+
return scheduledJobs;
|
|
1213
|
+
},
|
|
1214
|
+
/**
|
|
1215
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
1216
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
1217
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
1218
|
+
*/
|
|
1219
|
+
async syncFromDatabase() {
|
|
1220
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1221
|
+
where: {
|
|
1222
|
+
scheduledAt: {
|
|
1223
|
+
$gte: /* @__PURE__ */ new Date()
|
|
1224
|
+
},
|
|
1225
|
+
releasedAt: null
|
|
1226
|
+
}
|
|
652
1227
|
});
|
|
1228
|
+
for (const release2 of releases) {
|
|
1229
|
+
this.set(release2.id, release2.scheduledAt);
|
|
1230
|
+
}
|
|
1231
|
+
return scheduledJobs;
|
|
653
1232
|
}
|
|
654
1233
|
};
|
|
655
1234
|
};
|
|
656
1235
|
const services = {
|
|
657
1236
|
release: createReleaseService,
|
|
658
|
-
"release-action": createReleaseActionService,
|
|
659
1237
|
"release-validation": createReleaseValidationService,
|
|
660
|
-
|
|
1238
|
+
scheduling: createSchedulingService
|
|
661
1239
|
};
|
|
662
1240
|
const RELEASE_SCHEMA = yup__namespace.object().shape({
|
|
663
|
-
name: yup__namespace.string().trim().required()
|
|
1241
|
+
name: yup__namespace.string().trim().required(),
|
|
1242
|
+
scheduledAt: yup__namespace.string().nullable(),
|
|
1243
|
+
isScheduled: yup__namespace.boolean().optional(),
|
|
1244
|
+
time: yup__namespace.string().when("isScheduled", {
|
|
1245
|
+
is: true,
|
|
1246
|
+
then: yup__namespace.string().trim().required(),
|
|
1247
|
+
otherwise: yup__namespace.string().nullable()
|
|
1248
|
+
}),
|
|
1249
|
+
timezone: yup__namespace.string().when("isScheduled", {
|
|
1250
|
+
is: true,
|
|
1251
|
+
then: yup__namespace.string().required().nullable(),
|
|
1252
|
+
otherwise: yup__namespace.string().nullable()
|
|
1253
|
+
}),
|
|
1254
|
+
date: yup__namespace.string().when("isScheduled", {
|
|
1255
|
+
is: true,
|
|
1256
|
+
then: yup__namespace.string().required().nullable(),
|
|
1257
|
+
otherwise: yup__namespace.string().nullable()
|
|
1258
|
+
})
|
|
664
1259
|
}).required().noUnknown();
|
|
665
1260
|
const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
|
|
666
1261
|
const releaseController = {
|
|
@@ -677,9 +1272,7 @@ const releaseController = {
|
|
|
677
1272
|
const contentTypeUid = query.contentTypeUid;
|
|
678
1273
|
const entryId = query.entryId;
|
|
679
1274
|
const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
|
|
680
|
-
const data = await releaseService.
|
|
681
|
-
hasEntryAttached
|
|
682
|
-
});
|
|
1275
|
+
const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
|
|
683
1276
|
ctx.body = { data };
|
|
684
1277
|
} else {
|
|
685
1278
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
@@ -695,26 +1288,30 @@ const releaseController = {
|
|
|
695
1288
|
}
|
|
696
1289
|
};
|
|
697
1290
|
});
|
|
698
|
-
|
|
1291
|
+
const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
|
|
1292
|
+
where: {
|
|
1293
|
+
releasedAt: null
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
699
1297
|
}
|
|
700
1298
|
},
|
|
701
1299
|
async findOne(ctx) {
|
|
702
1300
|
const id = ctx.params.id;
|
|
703
1301
|
const releaseService = getService("release", { strapi });
|
|
704
1302
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
});
|
|
709
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
1303
|
+
if (!release2) {
|
|
1304
|
+
throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
|
|
1305
|
+
}
|
|
710
1306
|
const count = await releaseService.countActions({
|
|
711
1307
|
filters: {
|
|
712
1308
|
release: id
|
|
713
1309
|
}
|
|
714
1310
|
});
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1311
|
+
const sanitizedRelease = {
|
|
1312
|
+
...release2,
|
|
1313
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
1314
|
+
};
|
|
718
1315
|
const data = {
|
|
719
1316
|
...sanitizedRelease,
|
|
720
1317
|
actions: {
|
|
@@ -767,8 +1364,27 @@ const releaseController = {
|
|
|
767
1364
|
const id = ctx.params.id;
|
|
768
1365
|
const releaseService = getService("release", { strapi });
|
|
769
1366
|
const release2 = await releaseService.publish(id, { user });
|
|
1367
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1368
|
+
releaseService.countActions({
|
|
1369
|
+
filters: {
|
|
1370
|
+
release: id,
|
|
1371
|
+
type: "publish"
|
|
1372
|
+
}
|
|
1373
|
+
}),
|
|
1374
|
+
releaseService.countActions({
|
|
1375
|
+
filters: {
|
|
1376
|
+
release: id,
|
|
1377
|
+
type: "unpublish"
|
|
1378
|
+
}
|
|
1379
|
+
})
|
|
1380
|
+
]);
|
|
770
1381
|
ctx.body = {
|
|
771
|
-
data: release2
|
|
1382
|
+
data: release2,
|
|
1383
|
+
meta: {
|
|
1384
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1385
|
+
totalPublishedEntries: countPublishActions,
|
|
1386
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1387
|
+
}
|
|
772
1388
|
};
|
|
773
1389
|
}
|
|
774
1390
|
};
|
|
@@ -795,6 +1411,38 @@ const releaseActionController = {
|
|
|
795
1411
|
data: releaseAction2
|
|
796
1412
|
};
|
|
797
1413
|
},
|
|
1414
|
+
async createMany(ctx) {
|
|
1415
|
+
const releaseId = ctx.params.releaseId;
|
|
1416
|
+
const releaseActionsArgs = ctx.request.body;
|
|
1417
|
+
await Promise.all(
|
|
1418
|
+
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
|
1419
|
+
);
|
|
1420
|
+
const releaseService = getService("release", { strapi });
|
|
1421
|
+
const releaseActions = await strapi.db.transaction(async () => {
|
|
1422
|
+
const releaseActions2 = await Promise.all(
|
|
1423
|
+
releaseActionsArgs.map(async (releaseActionArgs) => {
|
|
1424
|
+
try {
|
|
1425
|
+
const action = await releaseService.createAction(releaseId, releaseActionArgs);
|
|
1426
|
+
return action;
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
if (error instanceof AlreadyOnReleaseError) {
|
|
1429
|
+
return null;
|
|
1430
|
+
}
|
|
1431
|
+
throw error;
|
|
1432
|
+
}
|
|
1433
|
+
})
|
|
1434
|
+
);
|
|
1435
|
+
return releaseActions2;
|
|
1436
|
+
});
|
|
1437
|
+
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
|
1438
|
+
ctx.body = {
|
|
1439
|
+
data: newReleaseActions,
|
|
1440
|
+
meta: {
|
|
1441
|
+
entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
|
|
1442
|
+
totalEntries: releaseActions.length
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
},
|
|
798
1446
|
async findMany(ctx) {
|
|
799
1447
|
const releaseId = ctx.params.releaseId;
|
|
800
1448
|
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
|
@@ -807,11 +1455,30 @@ const releaseActionController = {
|
|
|
807
1455
|
sort: query.groupBy === "action" ? "type" : query.groupBy,
|
|
808
1456
|
...query
|
|
809
1457
|
});
|
|
810
|
-
const
|
|
1458
|
+
const contentTypeOutputSanitizers = results.reduce((acc, action) => {
|
|
1459
|
+
if (acc[action.contentType]) {
|
|
1460
|
+
return acc;
|
|
1461
|
+
}
|
|
1462
|
+
const contentTypePermissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
|
1463
|
+
ability: ctx.state.userAbility,
|
|
1464
|
+
model: action.contentType
|
|
1465
|
+
});
|
|
1466
|
+
acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
|
|
1467
|
+
return acc;
|
|
1468
|
+
}, {});
|
|
1469
|
+
const sanitizedResults = await utils.mapAsync(results, async (action) => ({
|
|
1470
|
+
...action,
|
|
1471
|
+
entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
|
|
1472
|
+
}));
|
|
1473
|
+
const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
|
|
1474
|
+
const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
|
|
1475
|
+
const components = await releaseService.getAllComponents();
|
|
811
1476
|
ctx.body = {
|
|
812
1477
|
data: groupedData,
|
|
813
1478
|
meta: {
|
|
814
|
-
pagination
|
|
1479
|
+
pagination,
|
|
1480
|
+
contentTypes: contentTypes2,
|
|
1481
|
+
components
|
|
815
1482
|
}
|
|
816
1483
|
};
|
|
817
1484
|
},
|
|
@@ -961,6 +1628,22 @@ const releaseAction = {
|
|
|
961
1628
|
]
|
|
962
1629
|
}
|
|
963
1630
|
},
|
|
1631
|
+
{
|
|
1632
|
+
method: "POST",
|
|
1633
|
+
path: "/:releaseId/actions/bulk",
|
|
1634
|
+
handler: "release-action.createMany",
|
|
1635
|
+
config: {
|
|
1636
|
+
policies: [
|
|
1637
|
+
"admin::isAuthenticatedAdmin",
|
|
1638
|
+
{
|
|
1639
|
+
name: "admin::hasPermissions",
|
|
1640
|
+
config: {
|
|
1641
|
+
actions: ["plugin::content-releases.create-action"]
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
]
|
|
1645
|
+
}
|
|
1646
|
+
},
|
|
964
1647
|
{
|
|
965
1648
|
method: "GET",
|
|
966
1649
|
path: "/:releaseId/actions",
|
|
@@ -1017,22 +1700,21 @@ const routes = {
|
|
|
1017
1700
|
};
|
|
1018
1701
|
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
1019
1702
|
const getPlugin = () => {
|
|
1020
|
-
if (features.isEnabled("cms-content-releases")
|
|
1703
|
+
if (features.isEnabled("cms-content-releases")) {
|
|
1021
1704
|
return {
|
|
1022
1705
|
register,
|
|
1023
1706
|
bootstrap,
|
|
1707
|
+
destroy,
|
|
1024
1708
|
contentTypes,
|
|
1025
1709
|
services,
|
|
1026
1710
|
controllers,
|
|
1027
|
-
routes
|
|
1028
|
-
destroy() {
|
|
1029
|
-
if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
|
|
1030
|
-
getService("event-manager").destroyAllListeners();
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1711
|
+
routes
|
|
1033
1712
|
};
|
|
1034
1713
|
}
|
|
1035
1714
|
return {
|
|
1715
|
+
// Always return register, it handles its own feature check
|
|
1716
|
+
register,
|
|
1717
|
+
// Always return contentTypes to avoid losing data when the feature is disabled
|
|
1036
1718
|
contentTypes
|
|
1037
1719
|
};
|
|
1038
1720
|
};
|