@strapi/content-releases 0.0.0-experimental.b5b7b8260a4549f3bd7443fbd68be5ccc9857cd7 → 0.0.0-experimental.c5354c231bf2abb4fe353d2ab7812bced4a1c6fa
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{App-AYV8n9jv.js → App-OP70yd5M.js} +784 -435
- package/dist/_chunks/App-OP70yd5M.js.map +1 -0
- package/dist/_chunks/App-x6Tjj3HN.mjs +1315 -0
- package/dist/_chunks/App-x6Tjj3HN.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs +51 -0
- package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js +51 -0
- package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js.map +1 -0
- package/dist/_chunks/{en-gYDqKYFd.js → en-3SGjiVyR.js} +29 -7
- package/dist/_chunks/en-3SGjiVyR.js.map +1 -0
- package/dist/_chunks/{en-MyLPoISH.mjs → en-bpHsnU0n.mjs} +29 -7
- package/dist/_chunks/en-bpHsnU0n.mjs.map +1 -0
- package/dist/_chunks/{index-NFIckO1N.mjs → index-1ejXLtzt.mjs} +367 -48
- package/dist/_chunks/index-1ejXLtzt.mjs.map +1 -0
- package/dist/_chunks/{index-FYDbIuRi.js → index-ydocdaZ0.js} +355 -36
- package/dist/_chunks/index-ydocdaZ0.js.map +1 -0
- package/dist/admin/index.js +2 -2
- package/dist/admin/index.mjs +3 -3
- package/dist/server/index.js +1059 -377
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1060 -379
- package/dist/server/index.mjs.map +1 -1
- package/package.json +16 -11
- package/dist/_chunks/App-AYV8n9jv.js.map +0 -1
- package/dist/_chunks/App-KBaxZVmD.mjs +0 -967
- package/dist/_chunks/App-KBaxZVmD.mjs.map +0 -1
- package/dist/_chunks/en-MyLPoISH.mjs.map +0 -1
- package/dist/_chunks/en-gYDqKYFd.js.map +0 -1
- package/dist/_chunks/index-FYDbIuRi.js.map +0 -1
- package/dist/_chunks/index-NFIckO1N.mjs.map +0 -1
package/dist/server/index.mjs
CHANGED
|
@@ -1,6 +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";
|
|
3
5
|
import EE from "@strapi/strapi/dist/utils/ee";
|
|
6
|
+
import { scheduleJob } from "node-schedule";
|
|
4
7
|
import * as yup from "yup";
|
|
5
8
|
const RELEASE_MODEL_UID = "plugin::content-releases.release";
|
|
6
9
|
const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
|
|
@@ -48,47 +51,248 @@ const ACTIONS = [
|
|
|
48
51
|
pluginName: "content-releases"
|
|
49
52
|
}
|
|
50
53
|
];
|
|
54
|
+
const ALLOWED_WEBHOOK_EVENTS = {
|
|
55
|
+
RELEASES_PUBLISH: "releases.publish"
|
|
56
|
+
};
|
|
51
57
|
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
52
58
|
return strapi2.plugin("content-releases").service(name);
|
|
53
59
|
};
|
|
54
|
-
const {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
65
116
|
}
|
|
66
117
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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"
|
|
73
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
|
|
74
166
|
);
|
|
75
|
-
|
|
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();
|
|
76
261
|
}
|
|
77
262
|
};
|
|
78
263
|
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
79
264
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
80
|
-
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
|
+
);
|
|
81
269
|
strapi2.db.lifecycles.subscribe({
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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);
|
|
90
292
|
}
|
|
91
|
-
}
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
92
296
|
}
|
|
93
297
|
},
|
|
94
298
|
/**
|
|
@@ -97,7 +301,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
97
301
|
*/
|
|
98
302
|
async beforeDeleteMany(event) {
|
|
99
303
|
const { model, params } = event;
|
|
100
|
-
if (model.kind === "collectionType" && model.options
|
|
304
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
101
305
|
const { where } = params;
|
|
102
306
|
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
103
307
|
event.state.entriesToDelete = entriesToDelete;
|
|
@@ -108,20 +312,94 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
108
312
|
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
109
313
|
*/
|
|
110
314
|
async afterDeleteMany(event) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
+
}
|
|
119
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
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
for (const release2 of releases) {
|
|
340
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
120
341
|
}
|
|
342
|
+
}
|
|
343
|
+
} catch (error) {
|
|
344
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
345
|
+
error
|
|
121
346
|
});
|
|
122
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
|
+
}
|
|
123
384
|
}
|
|
124
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();
|
|
125
403
|
}
|
|
126
404
|
};
|
|
127
405
|
const schema$1 = {
|
|
@@ -150,6 +428,17 @@ const schema$1 = {
|
|
|
150
428
|
releasedAt: {
|
|
151
429
|
type: "datetime"
|
|
152
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
|
+
},
|
|
153
442
|
actions: {
|
|
154
443
|
type: "relation",
|
|
155
444
|
relation: "oneToMany",
|
|
@@ -202,6 +491,9 @@ const schema = {
|
|
|
202
491
|
relation: "manyToOne",
|
|
203
492
|
target: RELEASE_MODEL_UID,
|
|
204
493
|
inversedBy: "actions"
|
|
494
|
+
},
|
|
495
|
+
isEntryValid: {
|
|
496
|
+
type: "boolean"
|
|
205
497
|
}
|
|
206
498
|
}
|
|
207
499
|
};
|
|
@@ -212,361 +504,593 @@ const contentTypes = {
|
|
|
212
504
|
release: release$1,
|
|
213
505
|
"release-action": releaseAction$1
|
|
214
506
|
};
|
|
215
|
-
const createReleaseActionService = ({ strapi: strapi2 }) => ({
|
|
216
|
-
async deleteManyForContentType(contentTypeUid) {
|
|
217
|
-
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
218
|
-
where: {
|
|
219
|
-
target_type: contentTypeUid
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
507
|
const getGroupName = (queryValue) => {
|
|
225
508
|
switch (queryValue) {
|
|
226
509
|
case "contentType":
|
|
227
|
-
return "
|
|
510
|
+
return "contentType.displayName";
|
|
228
511
|
case "action":
|
|
229
512
|
return "type";
|
|
230
513
|
case "locale":
|
|
231
|
-
return _.getOr("No locale", "
|
|
514
|
+
return _.getOr("No locale", "locale.name");
|
|
232
515
|
default:
|
|
233
|
-
return "
|
|
516
|
+
return "contentType.displayName";
|
|
234
517
|
}
|
|
235
518
|
};
|
|
236
|
-
const createReleaseService = ({ strapi: strapi2 }) =>
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
242
525
|
});
|
|
243
|
-
}
|
|
244
|
-
async
|
|
245
|
-
const
|
|
246
|
-
|
|
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
|
|
247
557
|
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
},
|
|
253
580
|
populate: {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
count: true
|
|
581
|
+
entry: {
|
|
582
|
+
fields: ["id"]
|
|
257
583
|
}
|
|
258
584
|
}
|
|
259
585
|
});
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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: {
|
|
278
673
|
target_type: contentTypeUid,
|
|
279
674
|
target_id: entryId
|
|
280
675
|
}
|
|
281
676
|
}
|
|
282
|
-
},
|
|
283
|
-
{
|
|
284
|
-
actions: null
|
|
285
677
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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({
|
|
291
693
|
where: {
|
|
292
|
-
|
|
293
|
-
|
|
694
|
+
releasedAt: {
|
|
695
|
+
$null: true
|
|
696
|
+
},
|
|
697
|
+
actions: {
|
|
698
|
+
target_type: contentTypeUid,
|
|
699
|
+
target_id: entryId
|
|
700
|
+
}
|
|
294
701
|
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
+
}
|
|
302
718
|
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
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}`);
|
|
306
747
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (release2.actions?.length) {
|
|
310
|
-
const [actionForEntry] = release2.actions;
|
|
311
|
-
delete release2.actions;
|
|
312
|
-
return {
|
|
313
|
-
...release2,
|
|
314
|
-
action: actionForEntry
|
|
315
|
-
};
|
|
748
|
+
if (release2.releasedAt) {
|
|
749
|
+
throw new errors.ValidationError("Release already published");
|
|
316
750
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
/*
|
|
331
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
332
|
-
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
333
|
-
*/
|
|
334
|
-
// @ts-expect-error see above
|
|
335
|
-
data: releaseWithCreatorFields
|
|
336
|
-
});
|
|
337
|
-
return updatedRelease;
|
|
338
|
-
},
|
|
339
|
-
async createAction(releaseId, action) {
|
|
340
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
341
|
-
strapi: strapi2
|
|
342
|
-
});
|
|
343
|
-
await Promise.all([
|
|
344
|
-
validateEntryContentType(action.entry.contentType),
|
|
345
|
-
validateUniqueEntry(releaseId, action)
|
|
346
|
-
]);
|
|
347
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
348
|
-
if (!release2) {
|
|
349
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
350
|
-
}
|
|
351
|
-
if (release2.releasedAt) {
|
|
352
|
-
throw new errors.ValidationError("Release already published");
|
|
353
|
-
}
|
|
354
|
-
const { entry, type } = action;
|
|
355
|
-
return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
356
|
-
data: {
|
|
357
|
-
type,
|
|
358
|
-
contentType: entry.contentType,
|
|
359
|
-
locale: entry.locale,
|
|
360
|
-
entry: {
|
|
361
|
-
id: entry.id,
|
|
362
|
-
__type: entry.contentType,
|
|
363
|
-
__pivot: { field: "entry" }
|
|
364
|
-
},
|
|
365
|
-
release: releaseId
|
|
366
|
-
},
|
|
367
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
368
|
-
});
|
|
369
|
-
},
|
|
370
|
-
async findActions(releaseId, query) {
|
|
371
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
372
|
-
fields: ["id"]
|
|
373
|
-
});
|
|
374
|
-
if (!release2) {
|
|
375
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
376
|
-
}
|
|
377
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
378
|
-
...query,
|
|
379
|
-
populate: {
|
|
380
|
-
entry: true
|
|
381
|
-
},
|
|
382
|
-
filters: {
|
|
383
|
-
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);
|
|
384
764
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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}`);
|
|
394
780
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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" }
|
|
410
797
|
},
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
};
|
|
415
|
-
});
|
|
416
|
-
const groupName = getGroupName(groupBy);
|
|
417
|
-
return _.groupBy(groupName)(formattedData);
|
|
418
|
-
},
|
|
419
|
-
async getLocalesDataForActions() {
|
|
420
|
-
if (!strapi2.plugin("i18n")) {
|
|
421
|
-
return {};
|
|
422
|
-
}
|
|
423
|
-
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
424
|
-
return allLocales.reduce((acc, locale) => {
|
|
425
|
-
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
426
|
-
return acc;
|
|
427
|
-
}, {});
|
|
428
|
-
},
|
|
429
|
-
async getContentTypesDataForActions(contentTypesUids) {
|
|
430
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
431
|
-
const contentTypesData = {};
|
|
432
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
433
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
434
|
-
uid: contentTypeUid
|
|
798
|
+
release: releaseId
|
|
799
|
+
},
|
|
800
|
+
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
435
801
|
});
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
populate: {
|
|
446
|
-
actions: {
|
|
447
|
-
fields: ["id"]
|
|
448
|
-
}
|
|
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}`);
|
|
449
811
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
throw new errors.ValidationError("Release already published");
|
|
456
|
-
}
|
|
457
|
-
await strapi2.db.transaction(async () => {
|
|
458
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
459
|
-
where: {
|
|
460
|
-
id: {
|
|
461
|
-
$in: release2.actions.map((action) => action.id)
|
|
812
|
+
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
813
|
+
...query,
|
|
814
|
+
populate: {
|
|
815
|
+
entry: {
|
|
816
|
+
populate: "*"
|
|
462
817
|
}
|
|
818
|
+
},
|
|
819
|
+
filters: {
|
|
820
|
+
release: releaseId
|
|
463
821
|
}
|
|
464
822
|
});
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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, {
|
|
474
907
|
populate: {
|
|
475
908
|
actions: {
|
|
476
|
-
|
|
477
|
-
entry: true
|
|
478
|
-
}
|
|
909
|
+
fields: ["id"]
|
|
479
910
|
}
|
|
480
911
|
}
|
|
912
|
+
});
|
|
913
|
+
if (!release2) {
|
|
914
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
481
915
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
485
|
-
}
|
|
486
|
-
if (releaseWithPopulatedActionEntries.releasedAt) {
|
|
487
|
-
throw new errors.ValidationError("Release already published");
|
|
488
|
-
}
|
|
489
|
-
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
490
|
-
throw new errors.ValidationError("No entries to publish");
|
|
491
|
-
}
|
|
492
|
-
const actions = {};
|
|
493
|
-
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
494
|
-
const contentTypeUid = action.contentType;
|
|
495
|
-
if (!actions[contentTypeUid]) {
|
|
496
|
-
actions[contentTypeUid] = {
|
|
497
|
-
publish: [],
|
|
498
|
-
unpublish: []
|
|
499
|
-
};
|
|
916
|
+
if (release2.releasedAt) {
|
|
917
|
+
throw new errors.ValidationError("Release already published");
|
|
500
918
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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);
|
|
505
932
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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}`);
|
|
513
944
|
}
|
|
514
|
-
if (
|
|
515
|
-
|
|
945
|
+
if (lockedRelease.releasedAt) {
|
|
946
|
+
throw new errors.ValidationError("Release already published");
|
|
516
947
|
}
|
|
948
|
+
if (lockedRelease.status === "failed") {
|
|
949
|
+
throw new errors.ValidationError("Release failed to publish");
|
|
950
|
+
}
|
|
951
|
+
try {
|
|
952
|
+
strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
|
|
953
|
+
const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
|
|
954
|
+
releaseId
|
|
955
|
+
);
|
|
956
|
+
await strapi2.db.transaction(async () => {
|
|
957
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
958
|
+
await publishSingleTypeAction(uid, action, id);
|
|
959
|
+
}
|
|
960
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
961
|
+
const uid = contentTypeUid;
|
|
962
|
+
await publishCollectionTypeAction(
|
|
963
|
+
uid,
|
|
964
|
+
collectionTypeActions[uid].entriesToPublishIds,
|
|
965
|
+
collectionTypeActions[uid].entriesToUnpublishIds
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
970
|
+
where: {
|
|
971
|
+
id: releaseId
|
|
972
|
+
},
|
|
973
|
+
data: {
|
|
974
|
+
status: "done",
|
|
975
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
979
|
+
isPublished: true,
|
|
980
|
+
release: release22
|
|
981
|
+
});
|
|
982
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
983
|
+
return { release: release22, error: null };
|
|
984
|
+
} catch (error2) {
|
|
985
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
986
|
+
isPublished: false,
|
|
987
|
+
error: error2
|
|
988
|
+
});
|
|
989
|
+
await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
|
|
990
|
+
status: "failed"
|
|
991
|
+
}).transacting(trx).execute();
|
|
992
|
+
return {
|
|
993
|
+
release: null,
|
|
994
|
+
error: error2
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
if (error) {
|
|
999
|
+
throw error;
|
|
517
1000
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
+
);
|
|
526
1020
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
+
}
|
|
538
1032
|
}
|
|
539
1033
|
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
);
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
|
558
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
|
+
});
|
|
559
1067
|
}
|
|
1068
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1069
|
+
where: {
|
|
1070
|
+
id: releaseId
|
|
1071
|
+
},
|
|
1072
|
+
data: {
|
|
1073
|
+
status: "ready"
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
560
1076
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
1077
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1078
|
+
where: {
|
|
1079
|
+
id: releaseId
|
|
1080
|
+
},
|
|
1081
|
+
data: {
|
|
1082
|
+
status: "empty"
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
566
1085
|
}
|
|
567
|
-
|
|
1086
|
+
};
|
|
1087
|
+
};
|
|
1088
|
+
class AlreadyOnReleaseError extends errors.ApplicationError {
|
|
1089
|
+
constructor(message) {
|
|
1090
|
+
super(message);
|
|
1091
|
+
this.name = "AlreadyOnReleaseError";
|
|
568
1092
|
}
|
|
569
|
-
}
|
|
1093
|
+
}
|
|
570
1094
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
571
1095
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
572
1096
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
@@ -579,7 +1103,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
579
1103
|
(action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
|
|
580
1104
|
);
|
|
581
1105
|
if (isEntryInRelease) {
|
|
582
|
-
throw new
|
|
1106
|
+
throw new AlreadyOnReleaseError(
|
|
583
1107
|
`Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
|
|
584
1108
|
);
|
|
585
1109
|
}
|
|
@@ -610,34 +1134,104 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
610
1134
|
if (pendingReleasesCount >= maximumPendingReleases) {
|
|
611
1135
|
throw new errors.ValidationError("You have reached the maximum number of pending releases");
|
|
612
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
|
+
}
|
|
613
1157
|
}
|
|
614
1158
|
});
|
|
615
|
-
const
|
|
616
|
-
const
|
|
617
|
-
destroyListenerCallbacks: []
|
|
618
|
-
};
|
|
1159
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
1160
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
619
1161
|
return {
|
|
620
|
-
|
|
621
|
-
|
|
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;
|
|
622
1179
|
},
|
|
623
|
-
|
|
624
|
-
if (
|
|
625
|
-
|
|
1180
|
+
cancel(releaseId) {
|
|
1181
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1182
|
+
scheduledJobs.get(releaseId).cancel();
|
|
1183
|
+
scheduledJobs.delete(releaseId);
|
|
626
1184
|
}
|
|
627
|
-
|
|
628
|
-
|
|
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
|
+
}
|
|
629
1203
|
});
|
|
1204
|
+
for (const release2 of releases) {
|
|
1205
|
+
this.set(release2.id, release2.scheduledAt);
|
|
1206
|
+
}
|
|
1207
|
+
return scheduledJobs;
|
|
630
1208
|
}
|
|
631
1209
|
};
|
|
632
1210
|
};
|
|
633
1211
|
const services = {
|
|
634
1212
|
release: createReleaseService,
|
|
635
|
-
"release-action": createReleaseActionService,
|
|
636
1213
|
"release-validation": createReleaseValidationService,
|
|
637
|
-
|
|
1214
|
+
scheduling: createSchedulingService
|
|
638
1215
|
};
|
|
639
1216
|
const RELEASE_SCHEMA = yup.object().shape({
|
|
640
|
-
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
|
+
})
|
|
641
1235
|
}).required().noUnknown();
|
|
642
1236
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
643
1237
|
const releaseController = {
|
|
@@ -654,9 +1248,7 @@ const releaseController = {
|
|
|
654
1248
|
const contentTypeUid = query.contentTypeUid;
|
|
655
1249
|
const entryId = query.entryId;
|
|
656
1250
|
const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
|
|
657
|
-
const data = await releaseService.
|
|
658
|
-
hasEntryAttached
|
|
659
|
-
});
|
|
1251
|
+
const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
|
|
660
1252
|
ctx.body = { data };
|
|
661
1253
|
} else {
|
|
662
1254
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
@@ -672,26 +1264,30 @@ const releaseController = {
|
|
|
672
1264
|
}
|
|
673
1265
|
};
|
|
674
1266
|
});
|
|
675
|
-
|
|
1267
|
+
const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
|
|
1268
|
+
where: {
|
|
1269
|
+
releasedAt: null
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
676
1273
|
}
|
|
677
1274
|
},
|
|
678
1275
|
async findOne(ctx) {
|
|
679
1276
|
const id = ctx.params.id;
|
|
680
1277
|
const releaseService = getService("release", { strapi });
|
|
681
1278
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
});
|
|
686
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
1279
|
+
if (!release2) {
|
|
1280
|
+
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
1281
|
+
}
|
|
687
1282
|
const count = await releaseService.countActions({
|
|
688
1283
|
filters: {
|
|
689
1284
|
release: id
|
|
690
1285
|
}
|
|
691
1286
|
});
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
1287
|
+
const sanitizedRelease = {
|
|
1288
|
+
...release2,
|
|
1289
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
1290
|
+
};
|
|
695
1291
|
const data = {
|
|
696
1292
|
...sanitizedRelease,
|
|
697
1293
|
actions: {
|
|
@@ -744,8 +1340,27 @@ const releaseController = {
|
|
|
744
1340
|
const id = ctx.params.id;
|
|
745
1341
|
const releaseService = getService("release", { strapi });
|
|
746
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
|
+
]);
|
|
747
1357
|
ctx.body = {
|
|
748
|
-
data: release2
|
|
1358
|
+
data: release2,
|
|
1359
|
+
meta: {
|
|
1360
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1361
|
+
totalPublishedEntries: countPublishActions,
|
|
1362
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1363
|
+
}
|
|
749
1364
|
};
|
|
750
1365
|
}
|
|
751
1366
|
};
|
|
@@ -772,6 +1387,38 @@ const releaseActionController = {
|
|
|
772
1387
|
data: releaseAction2
|
|
773
1388
|
};
|
|
774
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
|
+
},
|
|
775
1422
|
async findMany(ctx) {
|
|
776
1423
|
const releaseId = ctx.params.releaseId;
|
|
777
1424
|
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
|
@@ -784,11 +1431,30 @@ const releaseActionController = {
|
|
|
784
1431
|
sort: query.groupBy === "action" ? "type" : query.groupBy,
|
|
785
1432
|
...query
|
|
786
1433
|
});
|
|
787
|
-
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();
|
|
788
1452
|
ctx.body = {
|
|
789
1453
|
data: groupedData,
|
|
790
1454
|
meta: {
|
|
791
|
-
pagination
|
|
1455
|
+
pagination,
|
|
1456
|
+
contentTypes: contentTypes2,
|
|
1457
|
+
components
|
|
792
1458
|
}
|
|
793
1459
|
};
|
|
794
1460
|
},
|
|
@@ -938,6 +1604,22 @@ const releaseAction = {
|
|
|
938
1604
|
]
|
|
939
1605
|
}
|
|
940
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
|
+
},
|
|
941
1623
|
{
|
|
942
1624
|
method: "GET",
|
|
943
1625
|
path: "/:releaseId/actions",
|
|
@@ -994,22 +1676,21 @@ const routes = {
|
|
|
994
1676
|
};
|
|
995
1677
|
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
996
1678
|
const getPlugin = () => {
|
|
997
|
-
if (features.isEnabled("cms-content-releases")
|
|
1679
|
+
if (features.isEnabled("cms-content-releases")) {
|
|
998
1680
|
return {
|
|
999
1681
|
register,
|
|
1000
1682
|
bootstrap,
|
|
1683
|
+
destroy,
|
|
1001
1684
|
contentTypes,
|
|
1002
1685
|
services,
|
|
1003
1686
|
controllers,
|
|
1004
|
-
routes
|
|
1005
|
-
destroy() {
|
|
1006
|
-
if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
|
|
1007
|
-
getService("event-manager").destroyAllListeners();
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1687
|
+
routes
|
|
1010
1688
|
};
|
|
1011
1689
|
}
|
|
1012
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
|
|
1013
1694
|
contentTypes
|
|
1014
1695
|
};
|
|
1015
1696
|
};
|