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