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