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