@strapi/content-releases 0.0.0-next.aa7c7ec6724534e157d8a23fe85ee8318dabbf37 → 0.0.0-next.ac2b9fdba5ef59eb22c4e387ac1c5a13dd219f29
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-pspKUC-W.js → App-dLXY5ei3.js} +639 -380
- package/dist/_chunks/App-dLXY5ei3.js.map +1 -0
- package/dist/_chunks/{App-8FCxPK8-.mjs → App-jrh58sXY.mjs} +650 -392
- 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-r9YocBH0.js → en-HrREghh3.js} +24 -5
- package/dist/_chunks/en-HrREghh3.js.map +1 -0
- package/dist/_chunks/{en-m9eTk4UF.mjs → en-ltT1TlKQ.mjs} +24 -5
- package/dist/_chunks/en-ltT1TlKQ.mjs.map +1 -0
- package/dist/_chunks/{index-nGaPcY9m.js → index-CVO0Rqdm.js} +398 -19
- package/dist/_chunks/index-CVO0Rqdm.js.map +1 -0
- package/dist/_chunks/{index-8aK7GzI5.mjs → index-PiOGBETy.mjs} +414 -35
- package/dist/_chunks/index-PiOGBETy.mjs.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +2 -2
- package/dist/server/index.js +960 -443
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +959 -443
- package/dist/server/index.mjs.map +1 -1
- package/package.json +17 -14
- package/dist/_chunks/App-8FCxPK8-.mjs.map +0 -1
- package/dist/_chunks/App-pspKUC-W.js.map +0 -1
- package/dist/_chunks/en-m9eTk4UF.mjs.map +0 -1
- package/dist/_chunks/en-r9YocBH0.js.map +0 -1
- package/dist/_chunks/index-8aK7GzI5.mjs.map +0 -1
- package/dist/_chunks/index-nGaPcY9m.js.map +0 -1
package/dist/server/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { contentTypes as contentTypes$1, mapAsync, setCreatorFields, errors, validateYupSchema, yup as yup$1 } from "@strapi/utils";
|
|
2
|
+
import isEqual from "lodash/isEqual";
|
|
2
3
|
import { difference, keys } from "lodash";
|
|
3
4
|
import _ from "lodash/fp";
|
|
4
5
|
import EE from "@strapi/strapi/dist/utils/ee";
|
|
@@ -50,6 +51,32 @@ const ACTIONS = [
|
|
|
50
51
|
pluginName: "content-releases"
|
|
51
52
|
}
|
|
52
53
|
];
|
|
54
|
+
const ALLOWED_WEBHOOK_EVENTS = {
|
|
55
|
+
RELEASES_PUBLISH: "releases.publish"
|
|
56
|
+
};
|
|
57
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
58
|
+
return strapi2.plugin("content-releases").service(name);
|
|
59
|
+
};
|
|
60
|
+
const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
61
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
62
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
63
|
+
const entry = await strapi2.entityService.findOne(contentTypeUid, entryId, { populate });
|
|
64
|
+
return entry;
|
|
65
|
+
};
|
|
66
|
+
const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
67
|
+
try {
|
|
68
|
+
await strapi2.entityValidator.validateEntityCreation(
|
|
69
|
+
strapi2.getModel(contentTypeUid),
|
|
70
|
+
entry,
|
|
71
|
+
void 0,
|
|
72
|
+
// @ts-expect-error - FIXME: entity here is unnecessary
|
|
73
|
+
entry
|
|
74
|
+
);
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
53
80
|
async function deleteActionsOnDisableDraftAndPublish({
|
|
54
81
|
oldContentTypes,
|
|
55
82
|
contentTypes: contentTypes2
|
|
@@ -76,31 +103,202 @@ async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes:
|
|
|
76
103
|
});
|
|
77
104
|
}
|
|
78
105
|
}
|
|
106
|
+
async function migrateIsValidAndStatusReleases() {
|
|
107
|
+
const releasesWithoutStatus = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
108
|
+
where: {
|
|
109
|
+
status: null,
|
|
110
|
+
releasedAt: null
|
|
111
|
+
},
|
|
112
|
+
populate: {
|
|
113
|
+
actions: {
|
|
114
|
+
populate: {
|
|
115
|
+
entry: true
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
mapAsync(releasesWithoutStatus, async (release2) => {
|
|
121
|
+
const actions = release2.actions;
|
|
122
|
+
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
123
|
+
for (const action of notValidatedActions) {
|
|
124
|
+
if (action.entry) {
|
|
125
|
+
const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
|
|
126
|
+
strapi
|
|
127
|
+
});
|
|
128
|
+
if (populatedEntry) {
|
|
129
|
+
const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
|
|
130
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
131
|
+
where: {
|
|
132
|
+
id: action.id
|
|
133
|
+
},
|
|
134
|
+
data: {
|
|
135
|
+
isEntryValid
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
142
|
+
});
|
|
143
|
+
const publishedReleases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
144
|
+
where: {
|
|
145
|
+
status: null,
|
|
146
|
+
releasedAt: {
|
|
147
|
+
$notNull: true
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
mapAsync(publishedReleases, async (release2) => {
|
|
152
|
+
return strapi.db.query(RELEASE_MODEL_UID).update({
|
|
153
|
+
where: {
|
|
154
|
+
id: release2.id
|
|
155
|
+
},
|
|
156
|
+
data: {
|
|
157
|
+
status: "done"
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
163
|
+
if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
|
|
164
|
+
const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
|
|
165
|
+
(uid) => oldContentTypes[uid]?.options?.draftAndPublish
|
|
166
|
+
);
|
|
167
|
+
const releasesAffected = /* @__PURE__ */ new Set();
|
|
168
|
+
mapAsync(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
|
|
169
|
+
const oldContentType = oldContentTypes[contentTypeUID];
|
|
170
|
+
const contentType = contentTypes2[contentTypeUID];
|
|
171
|
+
if (!isEqual(oldContentType?.attributes, contentType?.attributes)) {
|
|
172
|
+
const actions = await strapi.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
173
|
+
where: {
|
|
174
|
+
contentType: contentTypeUID
|
|
175
|
+
},
|
|
176
|
+
populate: {
|
|
177
|
+
entry: true,
|
|
178
|
+
release: true
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
await mapAsync(actions, async (action) => {
|
|
182
|
+
if (action.entry && action.release) {
|
|
183
|
+
const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
|
|
184
|
+
strapi
|
|
185
|
+
});
|
|
186
|
+
if (populatedEntry) {
|
|
187
|
+
const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
|
|
188
|
+
strapi
|
|
189
|
+
});
|
|
190
|
+
releasesAffected.add(action.release.id);
|
|
191
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
192
|
+
where: {
|
|
193
|
+
id: action.id
|
|
194
|
+
},
|
|
195
|
+
data: {
|
|
196
|
+
isEntryValid
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}).then(() => {
|
|
204
|
+
mapAsync(releasesAffected, async (releaseId) => {
|
|
205
|
+
return getService("release", { strapi }).updateReleaseStatus(releaseId);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
211
|
+
if (!oldContentTypes) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
215
|
+
if (!i18nPlugin) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
for (const uid in contentTypes2) {
|
|
219
|
+
if (!oldContentTypes[uid]) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const oldContentType = oldContentTypes[uid];
|
|
223
|
+
const contentType = contentTypes2[uid];
|
|
224
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
225
|
+
if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
|
|
226
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
227
|
+
locale: null
|
|
228
|
+
}).where({ contentType: uid }).execute();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
233
|
+
if (!oldContentTypes) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
237
|
+
if (!i18nPlugin) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
for (const uid in contentTypes2) {
|
|
241
|
+
if (!oldContentTypes[uid]) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const oldContentType = oldContentTypes[uid];
|
|
245
|
+
const contentType = contentTypes2[uid];
|
|
246
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
247
|
+
const { getDefaultLocale } = i18nPlugin.service("locales");
|
|
248
|
+
if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
|
|
249
|
+
const defaultLocale = await getDefaultLocale();
|
|
250
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
251
|
+
locale: defaultLocale
|
|
252
|
+
}).where({ contentType: uid }).execute();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
79
256
|
const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
|
|
80
257
|
const register = async ({ strapi: strapi2 }) => {
|
|
81
258
|
if (features$2.isEnabled("cms-content-releases")) {
|
|
82
259
|
await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
|
83
|
-
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
|
|
84
|
-
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
|
|
260
|
+
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish).register(disableContentTypeLocalized);
|
|
261
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
262
|
+
}
|
|
263
|
+
if (strapi2.plugin("graphql")) {
|
|
264
|
+
const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
|
|
265
|
+
graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
|
|
266
|
+
graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
|
|
85
267
|
}
|
|
86
|
-
};
|
|
87
|
-
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
88
|
-
return strapi2.plugin("content-releases").service(name);
|
|
89
268
|
};
|
|
90
269
|
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
91
270
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
92
271
|
if (features$1.isEnabled("cms-content-releases")) {
|
|
272
|
+
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
273
|
+
(uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
|
|
274
|
+
);
|
|
93
275
|
strapi2.db.lifecycles.subscribe({
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
276
|
+
models: contentTypesWithDraftAndPublish,
|
|
277
|
+
async afterDelete(event) {
|
|
278
|
+
try {
|
|
279
|
+
const { model, result } = event;
|
|
280
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
281
|
+
const { id } = result;
|
|
282
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
283
|
+
where: {
|
|
284
|
+
actions: {
|
|
285
|
+
target_type: model.uid,
|
|
286
|
+
target_id: id
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
291
|
+
where: {
|
|
292
|
+
target_type: model.uid,
|
|
293
|
+
target_id: id
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
for (const release2 of releases) {
|
|
297
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
102
298
|
}
|
|
103
|
-
}
|
|
299
|
+
}
|
|
300
|
+
} catch (error) {
|
|
301
|
+
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
104
302
|
}
|
|
105
303
|
},
|
|
106
304
|
/**
|
|
@@ -120,38 +318,94 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
120
318
|
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
121
319
|
*/
|
|
122
320
|
async afterDeleteMany(event) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
321
|
+
try {
|
|
322
|
+
const { model, state } = event;
|
|
323
|
+
const entriesToDelete = state.entriesToDelete;
|
|
324
|
+
if (entriesToDelete) {
|
|
325
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
326
|
+
where: {
|
|
327
|
+
actions: {
|
|
328
|
+
target_type: model.uid,
|
|
329
|
+
target_id: {
|
|
330
|
+
$in: entriesToDelete.map(
|
|
331
|
+
(entry) => entry.id
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
131
335
|
}
|
|
336
|
+
});
|
|
337
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
338
|
+
where: {
|
|
339
|
+
target_type: model.uid,
|
|
340
|
+
target_id: {
|
|
341
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
for (const release2 of releases) {
|
|
346
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
132
347
|
}
|
|
348
|
+
}
|
|
349
|
+
} catch (error) {
|
|
350
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
351
|
+
error
|
|
133
352
|
});
|
|
134
353
|
}
|
|
354
|
+
},
|
|
355
|
+
async afterUpdate(event) {
|
|
356
|
+
try {
|
|
357
|
+
const { model, result } = event;
|
|
358
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
359
|
+
const isEntryValid = await getEntryValidStatus(
|
|
360
|
+
model.uid,
|
|
361
|
+
result,
|
|
362
|
+
{
|
|
363
|
+
strapi: strapi2
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
367
|
+
where: {
|
|
368
|
+
target_type: model.uid,
|
|
369
|
+
target_id: result.id
|
|
370
|
+
},
|
|
371
|
+
data: {
|
|
372
|
+
isEntryValid
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
376
|
+
where: {
|
|
377
|
+
actions: {
|
|
378
|
+
target_type: model.uid,
|
|
379
|
+
target_id: result.id
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
for (const release2 of releases) {
|
|
384
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} catch (error) {
|
|
388
|
+
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
389
|
+
}
|
|
135
390
|
}
|
|
136
391
|
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
392
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
393
|
+
strapi2.log.error(
|
|
394
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
395
|
+
);
|
|
396
|
+
throw err;
|
|
397
|
+
});
|
|
398
|
+
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
399
|
+
strapi2.webhookStore.addAllowedEvent(key, value);
|
|
400
|
+
});
|
|
145
401
|
}
|
|
146
402
|
};
|
|
147
403
|
const destroy = async ({ strapi: strapi2 }) => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
job.cancel();
|
|
154
|
-
}
|
|
404
|
+
const scheduledJobs = getService("scheduling", {
|
|
405
|
+
strapi: strapi2
|
|
406
|
+
}).getAll();
|
|
407
|
+
for (const [, job] of scheduledJobs) {
|
|
408
|
+
job.cancel();
|
|
155
409
|
}
|
|
156
410
|
};
|
|
157
411
|
const schema$1 = {
|
|
@@ -183,6 +437,14 @@ const schema$1 = {
|
|
|
183
437
|
scheduledAt: {
|
|
184
438
|
type: "datetime"
|
|
185
439
|
},
|
|
440
|
+
timezone: {
|
|
441
|
+
type: "string"
|
|
442
|
+
},
|
|
443
|
+
status: {
|
|
444
|
+
type: "enumeration",
|
|
445
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
446
|
+
required: true
|
|
447
|
+
},
|
|
186
448
|
actions: {
|
|
187
449
|
type: "relation",
|
|
188
450
|
relation: "oneToMany",
|
|
@@ -235,6 +497,9 @@ const schema = {
|
|
|
235
497
|
relation: "manyToOne",
|
|
236
498
|
target: RELEASE_MODEL_UID,
|
|
237
499
|
inversedBy: "actions"
|
|
500
|
+
},
|
|
501
|
+
isEntryValid: {
|
|
502
|
+
type: "boolean"
|
|
238
503
|
}
|
|
239
504
|
}
|
|
240
505
|
};
|
|
@@ -257,464 +522,596 @@ const getGroupName = (queryValue) => {
|
|
|
257
522
|
return "contentType.displayName";
|
|
258
523
|
}
|
|
259
524
|
};
|
|
260
|
-
const createReleaseService = ({ strapi: strapi2 }) =>
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
validateScheduledAtIsLaterThanNow
|
|
267
|
-
} = getService("release-validation", { strapi: strapi2 });
|
|
268
|
-
await Promise.all([
|
|
269
|
-
validatePendingReleasesLimit(),
|
|
270
|
-
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
271
|
-
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
272
|
-
]);
|
|
273
|
-
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
274
|
-
data: releaseWithCreatorFields
|
|
525
|
+
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
526
|
+
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
527
|
+
strapi2.eventHub.emit(event, {
|
|
528
|
+
isPublished,
|
|
529
|
+
error,
|
|
530
|
+
release: release2
|
|
275
531
|
});
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
532
|
+
};
|
|
533
|
+
const publishSingleTypeAction = async (uid, actionType, entryId) => {
|
|
534
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
535
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
536
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
537
|
+
const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
|
|
538
|
+
try {
|
|
539
|
+
if (actionType === "publish") {
|
|
540
|
+
await entityManagerService.publish(entry, uid);
|
|
541
|
+
} else {
|
|
542
|
+
await entityManagerService.unpublish(entry, uid);
|
|
543
|
+
}
|
|
544
|
+
} catch (error) {
|
|
545
|
+
if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
546
|
+
;
|
|
547
|
+
else {
|
|
548
|
+
throw error;
|
|
549
|
+
}
|
|
279
550
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
551
|
+
};
|
|
552
|
+
const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
|
|
553
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
554
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
555
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
556
|
+
const entriesToPublish = await strapi2.entityService.findMany(uid, {
|
|
557
|
+
filters: {
|
|
558
|
+
id: {
|
|
559
|
+
$in: entriesToPublishIds
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
populate
|
|
285
563
|
});
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
...query,
|
|
291
|
-
populate: {
|
|
292
|
-
actions: {
|
|
293
|
-
// @ts-expect-error Ignore missing properties
|
|
294
|
-
count: true
|
|
564
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
|
|
565
|
+
filters: {
|
|
566
|
+
id: {
|
|
567
|
+
$in: entriestoUnpublishIds
|
|
295
568
|
}
|
|
296
|
-
}
|
|
569
|
+
},
|
|
570
|
+
populate
|
|
297
571
|
});
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
572
|
+
if (entriesToPublish.length > 0) {
|
|
573
|
+
await entityManagerService.publishMany(entriesToPublish, uid);
|
|
574
|
+
}
|
|
575
|
+
if (entriesToUnpublish.length > 0) {
|
|
576
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, uid);
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
const getFormattedActions = async (releaseId) => {
|
|
580
|
+
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
301
581
|
where: {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
target_id: entryId
|
|
305
|
-
},
|
|
306
|
-
releasedAt: {
|
|
307
|
-
$null: true
|
|
582
|
+
release: {
|
|
583
|
+
id: releaseId
|
|
308
584
|
}
|
|
309
585
|
},
|
|
310
586
|
populate: {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
where: {
|
|
314
|
-
target_type: contentTypeUid,
|
|
315
|
-
target_id: entryId
|
|
316
|
-
}
|
|
587
|
+
entry: {
|
|
588
|
+
fields: ["id"]
|
|
317
589
|
}
|
|
318
590
|
}
|
|
319
591
|
});
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
592
|
+
if (actions.length === 0) {
|
|
593
|
+
throw new errors.ValidationError("No entries to publish");
|
|
594
|
+
}
|
|
595
|
+
const collectionTypeActions = {};
|
|
596
|
+
const singleTypeActions = [];
|
|
597
|
+
for (const action of actions) {
|
|
598
|
+
const contentTypeUid = action.contentType;
|
|
599
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
600
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
601
|
+
collectionTypeActions[contentTypeUid] = {
|
|
602
|
+
entriesToPublishIds: [],
|
|
603
|
+
entriesToUnpublishIds: []
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
if (action.type === "publish") {
|
|
607
|
+
collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
|
|
608
|
+
} else {
|
|
609
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
610
|
+
}
|
|
611
|
+
} else {
|
|
612
|
+
singleTypeActions.push({
|
|
613
|
+
uid: contentTypeUid,
|
|
614
|
+
action: action.type,
|
|
615
|
+
id: action.entry.id
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return { collectionTypeActions, singleTypeActions };
|
|
620
|
+
};
|
|
621
|
+
return {
|
|
622
|
+
async create(releaseData, { user }) {
|
|
623
|
+
const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
|
|
624
|
+
const {
|
|
625
|
+
validatePendingReleasesLimit,
|
|
626
|
+
validateUniqueNameForPendingRelease,
|
|
627
|
+
validateScheduledAtIsLaterThanNow
|
|
628
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
629
|
+
await Promise.all([
|
|
630
|
+
validatePendingReleasesLimit(),
|
|
631
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
632
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
633
|
+
]);
|
|
634
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
635
|
+
data: {
|
|
636
|
+
...releaseWithCreatorFields,
|
|
637
|
+
status: "empty"
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
if (releaseWithCreatorFields.scheduledAt) {
|
|
641
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
642
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
328
643
|
}
|
|
644
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
329
645
|
return release2;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
646
|
+
},
|
|
647
|
+
async findOne(id, query = {}) {
|
|
648
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
649
|
+
...query
|
|
650
|
+
});
|
|
651
|
+
return release2;
|
|
652
|
+
},
|
|
653
|
+
findPage(query) {
|
|
654
|
+
return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
|
|
655
|
+
...query,
|
|
656
|
+
populate: {
|
|
657
|
+
actions: {
|
|
658
|
+
// @ts-expect-error Ignore missing properties
|
|
659
|
+
count: true
|
|
660
|
+
}
|
|
341
661
|
}
|
|
662
|
+
});
|
|
663
|
+
},
|
|
664
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entriesIds) {
|
|
665
|
+
let entries = entriesIds;
|
|
666
|
+
if (!Array.isArray(entriesIds)) {
|
|
667
|
+
entries = [entriesIds];
|
|
342
668
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
$notIn: releasesRelated.map((release2) => release2.id)
|
|
669
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
670
|
+
where: {
|
|
671
|
+
actions: {
|
|
672
|
+
target_type: contentTypeUid,
|
|
673
|
+
target_id: {
|
|
674
|
+
$in: entries
|
|
350
675
|
}
|
|
351
676
|
},
|
|
352
|
-
{
|
|
353
|
-
|
|
677
|
+
releasedAt: {
|
|
678
|
+
$null: true
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
populate: {
|
|
682
|
+
// Filter the action to get only the content type entry
|
|
683
|
+
actions: {
|
|
684
|
+
where: {
|
|
685
|
+
target_type: contentTypeUid,
|
|
686
|
+
target_id: {
|
|
687
|
+
$in: entries
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
populate: {
|
|
691
|
+
entry: {
|
|
692
|
+
select: ["id"]
|
|
693
|
+
}
|
|
694
|
+
}
|
|
354
695
|
}
|
|
355
|
-
],
|
|
356
|
-
releasedAt: {
|
|
357
|
-
$null: true
|
|
358
696
|
}
|
|
697
|
+
});
|
|
698
|
+
return releases.map((release2) => {
|
|
699
|
+
if (release2.actions?.length) {
|
|
700
|
+
const actionsForEntry = release2.actions;
|
|
701
|
+
delete release2.actions;
|
|
702
|
+
return {
|
|
703
|
+
...release2,
|
|
704
|
+
actions: actionsForEntry
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
return release2;
|
|
708
|
+
});
|
|
709
|
+
},
|
|
710
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
711
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
712
|
+
where: {
|
|
713
|
+
releasedAt: {
|
|
714
|
+
$null: true
|
|
715
|
+
},
|
|
716
|
+
actions: {
|
|
717
|
+
target_type: contentTypeUid,
|
|
718
|
+
target_id: entryId
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
723
|
+
where: {
|
|
724
|
+
$or: [
|
|
725
|
+
{
|
|
726
|
+
id: {
|
|
727
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
actions: null
|
|
732
|
+
}
|
|
733
|
+
],
|
|
734
|
+
releasedAt: {
|
|
735
|
+
$null: true
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
return releases.map((release2) => {
|
|
740
|
+
if (release2.actions?.length) {
|
|
741
|
+
const [actionForEntry] = release2.actions;
|
|
742
|
+
delete release2.actions;
|
|
743
|
+
return {
|
|
744
|
+
...release2,
|
|
745
|
+
action: actionForEntry
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
return release2;
|
|
749
|
+
});
|
|
750
|
+
},
|
|
751
|
+
async update(id, releaseData, { user }) {
|
|
752
|
+
const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(
|
|
753
|
+
releaseData
|
|
754
|
+
);
|
|
755
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
756
|
+
"release-validation",
|
|
757
|
+
{ strapi: strapi2 }
|
|
758
|
+
);
|
|
759
|
+
await Promise.all([
|
|
760
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
761
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
762
|
+
]);
|
|
763
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
764
|
+
if (!release2) {
|
|
765
|
+
throw new errors.NotFoundError(`No release found for id ${id}`);
|
|
359
766
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (release2.actions?.length) {
|
|
363
|
-
const [actionForEntry] = release2.actions;
|
|
364
|
-
delete release2.actions;
|
|
365
|
-
return {
|
|
366
|
-
...release2,
|
|
367
|
-
action: actionForEntry
|
|
368
|
-
};
|
|
767
|
+
if (release2.releasedAt) {
|
|
768
|
+
throw new errors.ValidationError("Release already published");
|
|
369
769
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
);
|
|
379
|
-
await Promise.all([
|
|
380
|
-
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
381
|
-
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
382
|
-
]);
|
|
383
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
384
|
-
if (!release2) {
|
|
385
|
-
throw new errors.NotFoundError(`No release found for id ${id}`);
|
|
386
|
-
}
|
|
387
|
-
if (release2.releasedAt) {
|
|
388
|
-
throw new errors.ValidationError("Release already published");
|
|
389
|
-
}
|
|
390
|
-
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
391
|
-
/*
|
|
392
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
393
|
-
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
394
|
-
*/
|
|
395
|
-
// @ts-expect-error see above
|
|
396
|
-
data: releaseWithCreatorFields
|
|
397
|
-
});
|
|
398
|
-
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
770
|
+
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
771
|
+
/*
|
|
772
|
+
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
773
|
+
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
774
|
+
*/
|
|
775
|
+
// @ts-expect-error see above
|
|
776
|
+
data: releaseWithCreatorFields
|
|
777
|
+
});
|
|
399
778
|
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
400
779
|
if (releaseData.scheduledAt) {
|
|
401
780
|
await schedulingService.set(id, releaseData.scheduledAt);
|
|
402
781
|
} else if (release2.scheduledAt) {
|
|
403
782
|
schedulingService.cancel(id);
|
|
404
783
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if (release2.releasedAt) {
|
|
421
|
-
throw new errors.ValidationError("Release already published");
|
|
422
|
-
}
|
|
423
|
-
const { entry, type } = action;
|
|
424
|
-
return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
425
|
-
data: {
|
|
426
|
-
type,
|
|
427
|
-
contentType: entry.contentType,
|
|
428
|
-
locale: entry.locale,
|
|
429
|
-
entry: {
|
|
430
|
-
id: entry.id,
|
|
431
|
-
__type: entry.contentType,
|
|
432
|
-
__pivot: { field: "entry" }
|
|
433
|
-
},
|
|
434
|
-
release: releaseId
|
|
435
|
-
},
|
|
436
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
437
|
-
});
|
|
438
|
-
},
|
|
439
|
-
async findActions(releaseId, query) {
|
|
440
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
441
|
-
fields: ["id"]
|
|
442
|
-
});
|
|
443
|
-
if (!release2) {
|
|
444
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
445
|
-
}
|
|
446
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
447
|
-
...query,
|
|
448
|
-
populate: {
|
|
449
|
-
entry: {
|
|
450
|
-
populate: "*"
|
|
451
|
-
}
|
|
452
|
-
},
|
|
453
|
-
filters: {
|
|
454
|
-
release: releaseId
|
|
784
|
+
this.updateReleaseStatus(id);
|
|
785
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
786
|
+
return updatedRelease;
|
|
787
|
+
},
|
|
788
|
+
async createAction(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
|
|
789
|
+
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
790
|
+
strapi: strapi2
|
|
791
|
+
});
|
|
792
|
+
await Promise.all([
|
|
793
|
+
validateEntryContentType(action.entry.contentType),
|
|
794
|
+
validateUniqueEntry(releaseId, action)
|
|
795
|
+
]);
|
|
796
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
797
|
+
if (!release2) {
|
|
798
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
455
799
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
async countActions(query) {
|
|
459
|
-
return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
|
|
460
|
-
},
|
|
461
|
-
async groupActions(actions, groupBy) {
|
|
462
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
463
|
-
if (!acc.includes(action.contentType)) {
|
|
464
|
-
acc.push(action.contentType);
|
|
800
|
+
if (release2.releasedAt) {
|
|
801
|
+
throw new errors.ValidationError("Release already published");
|
|
465
802
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
});
|
|
484
|
-
const groupName = getGroupName(groupBy);
|
|
485
|
-
return _.groupBy(groupName)(formattedData);
|
|
486
|
-
},
|
|
487
|
-
async getLocalesDataForActions() {
|
|
488
|
-
if (!strapi2.plugin("i18n")) {
|
|
489
|
-
return {};
|
|
490
|
-
}
|
|
491
|
-
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
492
|
-
return allLocales.reduce((acc, locale) => {
|
|
493
|
-
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
494
|
-
return acc;
|
|
495
|
-
}, {});
|
|
496
|
-
},
|
|
497
|
-
async getContentTypesDataForActions(contentTypesUids) {
|
|
498
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
499
|
-
const contentTypesData = {};
|
|
500
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
501
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
502
|
-
uid: contentTypeUid
|
|
803
|
+
const { entry, type } = action;
|
|
804
|
+
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
805
|
+
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
806
|
+
const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
807
|
+
data: {
|
|
808
|
+
type,
|
|
809
|
+
contentType: entry.contentType,
|
|
810
|
+
locale: entry.locale,
|
|
811
|
+
isEntryValid,
|
|
812
|
+
entry: {
|
|
813
|
+
id: entry.id,
|
|
814
|
+
__type: entry.contentType,
|
|
815
|
+
__pivot: { field: "entry" }
|
|
816
|
+
},
|
|
817
|
+
release: releaseId
|
|
818
|
+
},
|
|
819
|
+
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
503
820
|
});
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
return contentTypesData;
|
|
510
|
-
},
|
|
511
|
-
getContentTypeModelsFromActions(actions) {
|
|
512
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
513
|
-
if (!acc.includes(action.contentType)) {
|
|
514
|
-
acc.push(action.contentType);
|
|
821
|
+
if (!disableUpdateReleaseStatus) {
|
|
822
|
+
this.updateReleaseStatus(releaseId);
|
|
515
823
|
}
|
|
516
|
-
return
|
|
517
|
-
},
|
|
518
|
-
|
|
519
|
-
(
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
);
|
|
525
|
-
return contentTypeModelsMap;
|
|
526
|
-
},
|
|
527
|
-
async getAllComponents() {
|
|
528
|
-
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
529
|
-
const components = await contentManagerComponentsService.findAllComponents();
|
|
530
|
-
const componentsMap = components.reduce(
|
|
531
|
-
(acc, component) => {
|
|
532
|
-
acc[component.uid] = component;
|
|
533
|
-
return acc;
|
|
534
|
-
},
|
|
535
|
-
{}
|
|
536
|
-
);
|
|
537
|
-
return componentsMap;
|
|
538
|
-
},
|
|
539
|
-
async delete(releaseId) {
|
|
540
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
541
|
-
populate: {
|
|
542
|
-
actions: {
|
|
543
|
-
fields: ["id"]
|
|
544
|
-
}
|
|
824
|
+
return releaseAction2;
|
|
825
|
+
},
|
|
826
|
+
async findActions(releaseId, query) {
|
|
827
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
828
|
+
fields: ["id"]
|
|
829
|
+
});
|
|
830
|
+
if (!release2) {
|
|
831
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
545
832
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
throw new errors.ValidationError("Release already published");
|
|
552
|
-
}
|
|
553
|
-
await strapi2.db.transaction(async () => {
|
|
554
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
555
|
-
where: {
|
|
556
|
-
id: {
|
|
557
|
-
$in: release2.actions.map((action) => action.id)
|
|
833
|
+
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
834
|
+
...query,
|
|
835
|
+
populate: {
|
|
836
|
+
entry: {
|
|
837
|
+
populate: "*"
|
|
558
838
|
}
|
|
839
|
+
},
|
|
840
|
+
filters: {
|
|
841
|
+
release: releaseId
|
|
559
842
|
}
|
|
560
843
|
});
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
844
|
+
},
|
|
845
|
+
async countActions(query) {
|
|
846
|
+
return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
|
|
847
|
+
},
|
|
848
|
+
async groupActions(actions, groupBy) {
|
|
849
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
850
|
+
if (!acc.includes(action.contentType)) {
|
|
851
|
+
acc.push(action.contentType);
|
|
852
|
+
}
|
|
853
|
+
return acc;
|
|
854
|
+
}, []);
|
|
855
|
+
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
|
|
856
|
+
contentTypeUids
|
|
857
|
+
);
|
|
858
|
+
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
859
|
+
const formattedData = actions.map((action) => {
|
|
860
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
861
|
+
return {
|
|
862
|
+
...action,
|
|
863
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
864
|
+
contentType: {
|
|
865
|
+
displayName,
|
|
866
|
+
mainFieldValue: action.entry[mainField],
|
|
867
|
+
uid: action.contentType
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
});
|
|
871
|
+
const groupName = getGroupName(groupBy);
|
|
872
|
+
return _.groupBy(groupName)(formattedData);
|
|
873
|
+
},
|
|
874
|
+
async getLocalesDataForActions() {
|
|
875
|
+
if (!strapi2.plugin("i18n")) {
|
|
876
|
+
return {};
|
|
877
|
+
}
|
|
878
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
879
|
+
return allLocales.reduce((acc, locale) => {
|
|
880
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
881
|
+
return acc;
|
|
882
|
+
}, {});
|
|
883
|
+
},
|
|
884
|
+
async getContentTypesDataForActions(contentTypesUids) {
|
|
885
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
886
|
+
const contentTypesData = {};
|
|
887
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
888
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
889
|
+
uid: contentTypeUid
|
|
890
|
+
});
|
|
891
|
+
contentTypesData[contentTypeUid] = {
|
|
892
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
893
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
return contentTypesData;
|
|
897
|
+
},
|
|
898
|
+
getContentTypeModelsFromActions(actions) {
|
|
899
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
900
|
+
if (!acc.includes(action.contentType)) {
|
|
901
|
+
acc.push(action.contentType);
|
|
902
|
+
}
|
|
903
|
+
return acc;
|
|
904
|
+
}, []);
|
|
905
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
906
|
+
(acc, contentTypeUid) => {
|
|
907
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
908
|
+
return acc;
|
|
909
|
+
},
|
|
910
|
+
{}
|
|
911
|
+
);
|
|
912
|
+
return contentTypeModelsMap;
|
|
913
|
+
},
|
|
914
|
+
async getAllComponents() {
|
|
915
|
+
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
916
|
+
const components = await contentManagerComponentsService.findAllComponents();
|
|
917
|
+
const componentsMap = components.reduce(
|
|
918
|
+
(acc, component) => {
|
|
919
|
+
acc[component.uid] = component;
|
|
920
|
+
return acc;
|
|
921
|
+
},
|
|
922
|
+
{}
|
|
923
|
+
);
|
|
924
|
+
return componentsMap;
|
|
925
|
+
},
|
|
926
|
+
async delete(releaseId) {
|
|
927
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
570
928
|
populate: {
|
|
571
929
|
actions: {
|
|
572
|
-
|
|
573
|
-
entry: {
|
|
574
|
-
fields: ["id"]
|
|
575
|
-
}
|
|
576
|
-
}
|
|
930
|
+
fields: ["id"]
|
|
577
931
|
}
|
|
578
932
|
}
|
|
933
|
+
});
|
|
934
|
+
if (!release2) {
|
|
935
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
579
936
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const collectionTypeActions = {};
|
|
591
|
-
const singleTypeActions = [];
|
|
592
|
-
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
593
|
-
const contentTypeUid = action.contentType;
|
|
594
|
-
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
595
|
-
if (!collectionTypeActions[contentTypeUid]) {
|
|
596
|
-
collectionTypeActions[contentTypeUid] = {
|
|
597
|
-
entriestoPublishIds: [],
|
|
598
|
-
entriesToUnpublishIds: []
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
if (action.type === "publish") {
|
|
602
|
-
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
603
|
-
} else {
|
|
604
|
-
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
605
|
-
}
|
|
606
|
-
} else {
|
|
607
|
-
singleTypeActions.push({
|
|
608
|
-
uid: contentTypeUid,
|
|
609
|
-
action: action.type,
|
|
610
|
-
id: action.entry.id
|
|
937
|
+
if (release2.releasedAt) {
|
|
938
|
+
throw new errors.ValidationError("Release already published");
|
|
939
|
+
}
|
|
940
|
+
await strapi2.db.transaction(async () => {
|
|
941
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
942
|
+
where: {
|
|
943
|
+
id: {
|
|
944
|
+
$in: release2.actions.map((action) => action.id)
|
|
945
|
+
}
|
|
946
|
+
}
|
|
611
947
|
});
|
|
948
|
+
await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
|
949
|
+
});
|
|
950
|
+
if (release2.scheduledAt) {
|
|
951
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
952
|
+
await schedulingService.cancel(release2.id);
|
|
612
953
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
954
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
955
|
+
return release2;
|
|
956
|
+
},
|
|
957
|
+
async publish(releaseId) {
|
|
958
|
+
const {
|
|
959
|
+
release: release2,
|
|
960
|
+
error
|
|
961
|
+
} = await strapi2.db.transaction(async ({ trx }) => {
|
|
962
|
+
const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
|
|
963
|
+
if (!lockedRelease) {
|
|
964
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
965
|
+
}
|
|
966
|
+
if (lockedRelease.releasedAt) {
|
|
967
|
+
throw new errors.ValidationError("Release already published");
|
|
968
|
+
}
|
|
969
|
+
if (lockedRelease.status === "failed") {
|
|
970
|
+
throw new errors.ValidationError("Release failed to publish");
|
|
971
|
+
}
|
|
620
972
|
try {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
973
|
+
strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
|
|
974
|
+
const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
|
|
975
|
+
releaseId
|
|
976
|
+
);
|
|
977
|
+
await strapi2.db.transaction(async () => {
|
|
978
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
979
|
+
await publishSingleTypeAction(uid, action, id);
|
|
980
|
+
}
|
|
981
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
982
|
+
const uid = contentTypeUid;
|
|
983
|
+
await publishCollectionTypeAction(
|
|
984
|
+
uid,
|
|
985
|
+
collectionTypeActions[uid].entriesToPublishIds,
|
|
986
|
+
collectionTypeActions[uid].entriesToUnpublishIds
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
991
|
+
where: {
|
|
992
|
+
id: releaseId
|
|
993
|
+
},
|
|
994
|
+
data: {
|
|
995
|
+
status: "done",
|
|
996
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
1000
|
+
isPublished: true,
|
|
1001
|
+
release: release22
|
|
1002
|
+
});
|
|
1003
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
1004
|
+
return { release: release22, error: null };
|
|
1005
|
+
} catch (error2) {
|
|
1006
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
1007
|
+
isPublished: false,
|
|
1008
|
+
error: error2
|
|
1009
|
+
});
|
|
1010
|
+
await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
|
|
1011
|
+
status: "failed"
|
|
1012
|
+
}).transacting(trx).execute();
|
|
1013
|
+
return {
|
|
1014
|
+
release: null,
|
|
1015
|
+
error: error2
|
|
1016
|
+
};
|
|
632
1017
|
}
|
|
1018
|
+
});
|
|
1019
|
+
if (error) {
|
|
1020
|
+
throw error;
|
|
633
1021
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}
|
|
645
|
-
populate
|
|
1022
|
+
return release2;
|
|
1023
|
+
},
|
|
1024
|
+
async updateAction(actionId, releaseId, update) {
|
|
1025
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1026
|
+
where: {
|
|
1027
|
+
id: actionId,
|
|
1028
|
+
release: {
|
|
1029
|
+
id: releaseId,
|
|
1030
|
+
releasedAt: {
|
|
1031
|
+
$null: true
|
|
1032
|
+
}
|
|
646
1033
|
}
|
|
1034
|
+
},
|
|
1035
|
+
data: update
|
|
1036
|
+
});
|
|
1037
|
+
if (!updatedAction) {
|
|
1038
|
+
throw new errors.NotFoundError(
|
|
1039
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
647
1040
|
);
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
1041
|
+
}
|
|
1042
|
+
return updatedAction;
|
|
1043
|
+
},
|
|
1044
|
+
async deleteAction(actionId, releaseId) {
|
|
1045
|
+
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
1046
|
+
where: {
|
|
1047
|
+
id: actionId,
|
|
1048
|
+
release: {
|
|
1049
|
+
id: releaseId,
|
|
1050
|
+
releasedAt: {
|
|
1051
|
+
$null: true
|
|
1052
|
+
}
|
|
657
1053
|
}
|
|
658
|
-
);
|
|
659
|
-
if (entriesToPublish.length > 0) {
|
|
660
|
-
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
661
1054
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
|
|
668
|
-
data: {
|
|
669
|
-
/*
|
|
670
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
|
|
671
|
-
*/
|
|
672
|
-
// @ts-expect-error see above
|
|
673
|
-
releasedAt: /* @__PURE__ */ new Date()
|
|
1055
|
+
});
|
|
1056
|
+
if (!deletedAction) {
|
|
1057
|
+
throw new errors.NotFoundError(
|
|
1058
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1059
|
+
);
|
|
674
1060
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
id: releaseId,
|
|
684
|
-
releasedAt: {
|
|
685
|
-
$null: true
|
|
1061
|
+
this.updateReleaseStatus(releaseId);
|
|
1062
|
+
return deletedAction;
|
|
1063
|
+
},
|
|
1064
|
+
async updateReleaseStatus(releaseId) {
|
|
1065
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
1066
|
+
this.countActions({
|
|
1067
|
+
filters: {
|
|
1068
|
+
release: releaseId
|
|
686
1069
|
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
throw new errors.NotFoundError(
|
|
693
|
-
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
694
|
-
);
|
|
695
|
-
}
|
|
696
|
-
return updatedAction;
|
|
697
|
-
},
|
|
698
|
-
async deleteAction(actionId, releaseId) {
|
|
699
|
-
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
700
|
-
where: {
|
|
701
|
-
id: actionId,
|
|
702
|
-
release: {
|
|
703
|
-
id: releaseId,
|
|
704
|
-
releasedAt: {
|
|
705
|
-
$null: true
|
|
1070
|
+
}),
|
|
1071
|
+
this.countActions({
|
|
1072
|
+
filters: {
|
|
1073
|
+
release: releaseId,
|
|
1074
|
+
isEntryValid: false
|
|
706
1075
|
}
|
|
1076
|
+
})
|
|
1077
|
+
]);
|
|
1078
|
+
if (totalActions > 0) {
|
|
1079
|
+
if (invalidActions > 0) {
|
|
1080
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1081
|
+
where: {
|
|
1082
|
+
id: releaseId
|
|
1083
|
+
},
|
|
1084
|
+
data: {
|
|
1085
|
+
status: "blocked"
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
707
1088
|
}
|
|
1089
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1090
|
+
where: {
|
|
1091
|
+
id: releaseId
|
|
1092
|
+
},
|
|
1093
|
+
data: {
|
|
1094
|
+
status: "ready"
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
708
1097
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
1098
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1099
|
+
where: {
|
|
1100
|
+
id: releaseId
|
|
1101
|
+
},
|
|
1102
|
+
data: {
|
|
1103
|
+
status: "empty"
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
714
1106
|
}
|
|
715
|
-
|
|
1107
|
+
};
|
|
1108
|
+
};
|
|
1109
|
+
class AlreadyOnReleaseError extends errors.ApplicationError {
|
|
1110
|
+
constructor(message) {
|
|
1111
|
+
super(message);
|
|
1112
|
+
this.name = "AlreadyOnReleaseError";
|
|
716
1113
|
}
|
|
717
|
-
}
|
|
1114
|
+
}
|
|
718
1115
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
719
1116
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
720
1117
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
@@ -727,7 +1124,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
727
1124
|
(action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
|
|
728
1125
|
);
|
|
729
1126
|
if (isEntryInRelease) {
|
|
730
|
-
throw new
|
|
1127
|
+
throw new AlreadyOnReleaseError(
|
|
731
1128
|
`Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
|
|
732
1129
|
);
|
|
733
1130
|
}
|
|
@@ -835,12 +1232,27 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
|
835
1232
|
const services = {
|
|
836
1233
|
release: createReleaseService,
|
|
837
1234
|
"release-validation": createReleaseValidationService,
|
|
838
|
-
|
|
1235
|
+
scheduling: createSchedulingService
|
|
839
1236
|
};
|
|
840
1237
|
const RELEASE_SCHEMA = yup.object().shape({
|
|
841
1238
|
name: yup.string().trim().required(),
|
|
842
|
-
|
|
843
|
-
|
|
1239
|
+
scheduledAt: yup.string().nullable(),
|
|
1240
|
+
isScheduled: yup.boolean().optional(),
|
|
1241
|
+
time: yup.string().when("isScheduled", {
|
|
1242
|
+
is: true,
|
|
1243
|
+
then: yup.string().trim().required(),
|
|
1244
|
+
otherwise: yup.string().nullable()
|
|
1245
|
+
}),
|
|
1246
|
+
timezone: yup.string().when("isScheduled", {
|
|
1247
|
+
is: true,
|
|
1248
|
+
then: yup.string().required().nullable(),
|
|
1249
|
+
otherwise: yup.string().nullable()
|
|
1250
|
+
}),
|
|
1251
|
+
date: yup.string().when("isScheduled", {
|
|
1252
|
+
is: true,
|
|
1253
|
+
then: yup.string().required().nullable(),
|
|
1254
|
+
otherwise: yup.string().nullable()
|
|
1255
|
+
})
|
|
844
1256
|
}).required().noUnknown();
|
|
845
1257
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
846
1258
|
const releaseController = {
|
|
@@ -873,7 +1285,12 @@ const releaseController = {
|
|
|
873
1285
|
}
|
|
874
1286
|
};
|
|
875
1287
|
});
|
|
876
|
-
|
|
1288
|
+
const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
|
|
1289
|
+
where: {
|
|
1290
|
+
releasedAt: null
|
|
1291
|
+
}
|
|
1292
|
+
});
|
|
1293
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
877
1294
|
}
|
|
878
1295
|
},
|
|
879
1296
|
async findOne(ctx) {
|
|
@@ -902,6 +1319,33 @@ const releaseController = {
|
|
|
902
1319
|
};
|
|
903
1320
|
ctx.body = { data };
|
|
904
1321
|
},
|
|
1322
|
+
async mapEntriesToReleases(ctx) {
|
|
1323
|
+
const { contentTypeUid, entriesIds } = ctx.query;
|
|
1324
|
+
if (!contentTypeUid || !entriesIds) {
|
|
1325
|
+
throw new errors.ValidationError("Missing required query parameters");
|
|
1326
|
+
}
|
|
1327
|
+
const releaseService = getService("release", { strapi });
|
|
1328
|
+
const releasesWithActions = await releaseService.findManyWithContentTypeEntryAttached(
|
|
1329
|
+
contentTypeUid,
|
|
1330
|
+
entriesIds
|
|
1331
|
+
);
|
|
1332
|
+
const mappedEntriesInReleases = releasesWithActions.reduce(
|
|
1333
|
+
(acc, release2) => {
|
|
1334
|
+
release2.actions.forEach((action) => {
|
|
1335
|
+
if (!acc[action.entry.id]) {
|
|
1336
|
+
acc[action.entry.id] = [{ id: release2.id, name: release2.name }];
|
|
1337
|
+
} else {
|
|
1338
|
+
acc[action.entry.id].push({ id: release2.id, name: release2.name });
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1341
|
+
return acc;
|
|
1342
|
+
},
|
|
1343
|
+
{}
|
|
1344
|
+
);
|
|
1345
|
+
ctx.body = {
|
|
1346
|
+
data: mappedEntriesInReleases
|
|
1347
|
+
};
|
|
1348
|
+
},
|
|
905
1349
|
async create(ctx) {
|
|
906
1350
|
const user = ctx.state.user;
|
|
907
1351
|
const releaseArgs = ctx.request.body;
|
|
@@ -991,6 +1435,43 @@ const releaseActionController = {
|
|
|
991
1435
|
data: releaseAction2
|
|
992
1436
|
};
|
|
993
1437
|
},
|
|
1438
|
+
async createMany(ctx) {
|
|
1439
|
+
const releaseId = ctx.params.releaseId;
|
|
1440
|
+
const releaseActionsArgs = ctx.request.body;
|
|
1441
|
+
await Promise.all(
|
|
1442
|
+
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
|
1443
|
+
);
|
|
1444
|
+
const releaseService = getService("release", { strapi });
|
|
1445
|
+
const releaseActions = await strapi.db.transaction(async () => {
|
|
1446
|
+
const releaseActions2 = await Promise.all(
|
|
1447
|
+
releaseActionsArgs.map(async (releaseActionArgs) => {
|
|
1448
|
+
try {
|
|
1449
|
+
const action = await releaseService.createAction(releaseId, releaseActionArgs, {
|
|
1450
|
+
disableUpdateReleaseStatus: true
|
|
1451
|
+
});
|
|
1452
|
+
return action;
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
if (error instanceof AlreadyOnReleaseError) {
|
|
1455
|
+
return null;
|
|
1456
|
+
}
|
|
1457
|
+
throw error;
|
|
1458
|
+
}
|
|
1459
|
+
})
|
|
1460
|
+
);
|
|
1461
|
+
return releaseActions2;
|
|
1462
|
+
});
|
|
1463
|
+
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
|
1464
|
+
if (newReleaseActions.length > 0) {
|
|
1465
|
+
releaseService.updateReleaseStatus(releaseId);
|
|
1466
|
+
}
|
|
1467
|
+
ctx.body = {
|
|
1468
|
+
data: newReleaseActions,
|
|
1469
|
+
meta: {
|
|
1470
|
+
entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
|
|
1471
|
+
totalEntries: releaseActions.length
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
},
|
|
994
1475
|
async findMany(ctx) {
|
|
995
1476
|
const releaseId = ctx.params.releaseId;
|
|
996
1477
|
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
|
@@ -1059,6 +1540,22 @@ const controllers = { release: releaseController, "release-action": releaseActio
|
|
|
1059
1540
|
const release = {
|
|
1060
1541
|
type: "admin",
|
|
1061
1542
|
routes: [
|
|
1543
|
+
{
|
|
1544
|
+
method: "GET",
|
|
1545
|
+
path: "/mapEntriesToReleases",
|
|
1546
|
+
handler: "release.mapEntriesToReleases",
|
|
1547
|
+
config: {
|
|
1548
|
+
policies: [
|
|
1549
|
+
"admin::isAuthenticatedAdmin",
|
|
1550
|
+
{
|
|
1551
|
+
name: "admin::hasPermissions",
|
|
1552
|
+
config: {
|
|
1553
|
+
actions: ["plugin::content-releases.read"]
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
]
|
|
1557
|
+
}
|
|
1558
|
+
},
|
|
1062
1559
|
{
|
|
1063
1560
|
method: "POST",
|
|
1064
1561
|
path: "/",
|
|
@@ -1176,6 +1673,22 @@ const releaseAction = {
|
|
|
1176
1673
|
]
|
|
1177
1674
|
}
|
|
1178
1675
|
},
|
|
1676
|
+
{
|
|
1677
|
+
method: "POST",
|
|
1678
|
+
path: "/:releaseId/actions/bulk",
|
|
1679
|
+
handler: "release-action.createMany",
|
|
1680
|
+
config: {
|
|
1681
|
+
policies: [
|
|
1682
|
+
"admin::isAuthenticatedAdmin",
|
|
1683
|
+
{
|
|
1684
|
+
name: "admin::hasPermissions",
|
|
1685
|
+
config: {
|
|
1686
|
+
actions: ["plugin::content-releases.create-action"]
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
]
|
|
1690
|
+
}
|
|
1691
|
+
},
|
|
1179
1692
|
{
|
|
1180
1693
|
method: "GET",
|
|
1181
1694
|
path: "/:releaseId/actions",
|
|
@@ -1244,6 +1757,9 @@ const getPlugin = () => {
|
|
|
1244
1757
|
};
|
|
1245
1758
|
}
|
|
1246
1759
|
return {
|
|
1760
|
+
// Always return register, it handles its own feature check
|
|
1761
|
+
register,
|
|
1762
|
+
// Always return contentTypes to avoid losing data when the feature is disabled
|
|
1247
1763
|
contentTypes
|
|
1248
1764
|
};
|
|
1249
1765
|
};
|