@strapi/content-releases 0.0.0-next.6d384ed205b7f0792d9bea79195f01b30463cfa0 → 0.0.0-next.73143c28059b343ba62d98c29672ab114562fbbc
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-WkwjSaDY.mjs → App-g3vtS2Wa.mjs} +385 -174
- package/dist/_chunks/App-g3vtS2Wa.mjs.map +1 -0
- package/dist/_chunks/{App-IQIHCiSO.js → App-lnXbSPgp.js} +383 -171
- package/dist/_chunks/App-lnXbSPgp.js.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs +51 -0
- package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js +51 -0
- package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +1 -0
- package/dist/_chunks/{en-m9eTk4UF.mjs → en-WuuhP6Bn.mjs} +15 -4
- package/dist/_chunks/en-WuuhP6Bn.mjs.map +1 -0
- package/dist/_chunks/{en-r9YocBH0.js → en-gcJJ5htG.js} +15 -4
- package/dist/_chunks/en-gcJJ5htG.js.map +1 -0
- package/dist/_chunks/{index-u_da7WHb.js → index-ItlgrLcr.js} +122 -19
- package/dist/_chunks/index-ItlgrLcr.js.map +1 -0
- package/dist/_chunks/{index-CyVlvX8h.mjs → index-uGex_IIQ.mjs} +126 -23
- package/dist/_chunks/index-uGex_IIQ.mjs.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +2 -2
- package/dist/server/index.js +814 -418
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +813 -418
- package/dist/server/index.mjs.map +1 -1
- package/package.json +13 -11
- package/dist/_chunks/App-IQIHCiSO.js.map +0 -1
- package/dist/_chunks/App-WkwjSaDY.mjs.map +0 -1
- package/dist/_chunks/en-m9eTk4UF.mjs.map +0 -1
- package/dist/_chunks/en-r9YocBH0.js.map +0 -1
- package/dist/_chunks/index-CyVlvX8h.mjs.map +0 -1
- package/dist/_chunks/index-u_da7WHb.js.map +0 -1
package/dist/server/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { contentTypes as contentTypes$1, mapAsync, setCreatorFields, errors, validateYupSchema, yup as yup$1 } from "@strapi/utils";
|
|
2
|
+
import isEqual from "lodash/isEqual";
|
|
2
3
|
import { difference, keys } from "lodash";
|
|
3
4
|
import _ from "lodash/fp";
|
|
4
5
|
import EE from "@strapi/strapi/dist/utils/ee";
|
|
@@ -50,6 +51,32 @@ const ACTIONS = [
|
|
|
50
51
|
pluginName: "content-releases"
|
|
51
52
|
}
|
|
52
53
|
];
|
|
54
|
+
const ALLOWED_WEBHOOK_EVENTS = {
|
|
55
|
+
RELEASES_PUBLISH: "releases.publish"
|
|
56
|
+
};
|
|
57
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
58
|
+
return strapi2.plugin("content-releases").service(name);
|
|
59
|
+
};
|
|
60
|
+
const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
61
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
62
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
63
|
+
const entry = await strapi2.entityService.findOne(contentTypeUid, entryId, { populate });
|
|
64
|
+
return entry;
|
|
65
|
+
};
|
|
66
|
+
const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
67
|
+
try {
|
|
68
|
+
await strapi2.entityValidator.validateEntityCreation(
|
|
69
|
+
strapi2.getModel(contentTypeUid),
|
|
70
|
+
entry,
|
|
71
|
+
void 0,
|
|
72
|
+
// @ts-expect-error - FIXME: entity here is unnecessary
|
|
73
|
+
entry
|
|
74
|
+
);
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
53
80
|
async function deleteActionsOnDisableDraftAndPublish({
|
|
54
81
|
oldContentTypes,
|
|
55
82
|
contentTypes: contentTypes2
|
|
@@ -76,28 +103,151 @@ async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes:
|
|
|
76
103
|
});
|
|
77
104
|
}
|
|
78
105
|
}
|
|
106
|
+
async function migrateIsValidAndStatusReleases() {
|
|
107
|
+
const releasesWithoutStatus = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
108
|
+
where: {
|
|
109
|
+
status: null,
|
|
110
|
+
releasedAt: null
|
|
111
|
+
},
|
|
112
|
+
populate: {
|
|
113
|
+
actions: {
|
|
114
|
+
populate: {
|
|
115
|
+
entry: true
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
mapAsync(releasesWithoutStatus, async (release2) => {
|
|
121
|
+
const actions = release2.actions;
|
|
122
|
+
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
123
|
+
for (const action of notValidatedActions) {
|
|
124
|
+
if (action.entry) {
|
|
125
|
+
const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
|
|
126
|
+
strapi
|
|
127
|
+
});
|
|
128
|
+
if (populatedEntry) {
|
|
129
|
+
const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
|
|
130
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
131
|
+
where: {
|
|
132
|
+
id: action.id
|
|
133
|
+
},
|
|
134
|
+
data: {
|
|
135
|
+
isEntryValid
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
142
|
+
});
|
|
143
|
+
const publishedReleases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
144
|
+
where: {
|
|
145
|
+
status: null,
|
|
146
|
+
releasedAt: {
|
|
147
|
+
$notNull: true
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
mapAsync(publishedReleases, async (release2) => {
|
|
152
|
+
return strapi.db.query(RELEASE_MODEL_UID).update({
|
|
153
|
+
where: {
|
|
154
|
+
id: release2.id
|
|
155
|
+
},
|
|
156
|
+
data: {
|
|
157
|
+
status: "done"
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
163
|
+
if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
|
|
164
|
+
const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
|
|
165
|
+
(uid) => oldContentTypes[uid]?.options?.draftAndPublish
|
|
166
|
+
);
|
|
167
|
+
const releasesAffected = /* @__PURE__ */ new Set();
|
|
168
|
+
mapAsync(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
|
|
169
|
+
const oldContentType = oldContentTypes[contentTypeUID];
|
|
170
|
+
const contentType = contentTypes2[contentTypeUID];
|
|
171
|
+
if (!isEqual(oldContentType?.attributes, contentType?.attributes)) {
|
|
172
|
+
const actions = await strapi.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
173
|
+
where: {
|
|
174
|
+
contentType: contentTypeUID
|
|
175
|
+
},
|
|
176
|
+
populate: {
|
|
177
|
+
entry: true,
|
|
178
|
+
release: true
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
await mapAsync(actions, async (action) => {
|
|
182
|
+
if (action.entry) {
|
|
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
|
+
}
|
|
79
210
|
const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
|
|
80
211
|
const register = async ({ strapi: strapi2 }) => {
|
|
81
212
|
if (features$2.isEnabled("cms-content-releases")) {
|
|
82
213
|
await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
|
83
214
|
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
|
|
84
|
-
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
|
|
215
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
85
216
|
}
|
|
86
217
|
};
|
|
87
218
|
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
88
219
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
89
220
|
if (features$1.isEnabled("cms-content-releases")) {
|
|
221
|
+
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
222
|
+
(uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
|
|
223
|
+
);
|
|
90
224
|
strapi2.db.lifecycles.subscribe({
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
225
|
+
models: contentTypesWithDraftAndPublish,
|
|
226
|
+
async afterDelete(event) {
|
|
227
|
+
try {
|
|
228
|
+
const { model, result } = event;
|
|
229
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
230
|
+
const { id } = result;
|
|
231
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
232
|
+
where: {
|
|
233
|
+
actions: {
|
|
234
|
+
target_type: model.uid,
|
|
235
|
+
target_id: id
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
240
|
+
where: {
|
|
241
|
+
target_type: model.uid,
|
|
242
|
+
target_id: id
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
for (const release2 of releases) {
|
|
246
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
99
247
|
}
|
|
100
|
-
}
|
|
248
|
+
}
|
|
249
|
+
} catch (error) {
|
|
250
|
+
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
101
251
|
}
|
|
102
252
|
},
|
|
103
253
|
/**
|
|
@@ -117,20 +267,98 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
117
267
|
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
118
268
|
*/
|
|
119
269
|
async afterDeleteMany(event) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
270
|
+
try {
|
|
271
|
+
const { model, state } = event;
|
|
272
|
+
const entriesToDelete = state.entriesToDelete;
|
|
273
|
+
if (entriesToDelete) {
|
|
274
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
275
|
+
where: {
|
|
276
|
+
actions: {
|
|
277
|
+
target_type: model.uid,
|
|
278
|
+
target_id: {
|
|
279
|
+
$in: entriesToDelete.map(
|
|
280
|
+
(entry) => entry.id
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
128
284
|
}
|
|
285
|
+
});
|
|
286
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
287
|
+
where: {
|
|
288
|
+
target_type: model.uid,
|
|
289
|
+
target_id: {
|
|
290
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
for (const release2 of releases) {
|
|
295
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
129
296
|
}
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
300
|
+
error
|
|
130
301
|
});
|
|
131
302
|
}
|
|
303
|
+
},
|
|
304
|
+
async afterUpdate(event) {
|
|
305
|
+
try {
|
|
306
|
+
const { model, result } = event;
|
|
307
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
308
|
+
const isEntryValid = await getEntryValidStatus(
|
|
309
|
+
model.uid,
|
|
310
|
+
result,
|
|
311
|
+
{
|
|
312
|
+
strapi: strapi2
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
316
|
+
where: {
|
|
317
|
+
target_type: model.uid,
|
|
318
|
+
target_id: result.id
|
|
319
|
+
},
|
|
320
|
+
data: {
|
|
321
|
+
isEntryValid
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
325
|
+
where: {
|
|
326
|
+
actions: {
|
|
327
|
+
target_type: model.uid,
|
|
328
|
+
target_id: result.id
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
for (const release2 of releases) {
|
|
333
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
} catch (error) {
|
|
337
|
+
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
338
|
+
}
|
|
132
339
|
}
|
|
133
340
|
});
|
|
341
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
342
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
343
|
+
strapi2.log.error(
|
|
344
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
345
|
+
);
|
|
346
|
+
throw err;
|
|
347
|
+
});
|
|
348
|
+
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
349
|
+
strapi2.webhookStore.addAllowedEvent(key, value);
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
const destroy = async ({ strapi: strapi2 }) => {
|
|
355
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
356
|
+
const scheduledJobs = getService("scheduling", {
|
|
357
|
+
strapi: strapi2
|
|
358
|
+
}).getAll();
|
|
359
|
+
for (const [, job] of scheduledJobs) {
|
|
360
|
+
job.cancel();
|
|
361
|
+
}
|
|
134
362
|
}
|
|
135
363
|
};
|
|
136
364
|
const schema$1 = {
|
|
@@ -162,6 +390,14 @@ const schema$1 = {
|
|
|
162
390
|
scheduledAt: {
|
|
163
391
|
type: "datetime"
|
|
164
392
|
},
|
|
393
|
+
timezone: {
|
|
394
|
+
type: "string"
|
|
395
|
+
},
|
|
396
|
+
status: {
|
|
397
|
+
type: "enumeration",
|
|
398
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
399
|
+
required: true
|
|
400
|
+
},
|
|
165
401
|
actions: {
|
|
166
402
|
type: "relation",
|
|
167
403
|
relation: "oneToMany",
|
|
@@ -214,6 +450,9 @@ const schema = {
|
|
|
214
450
|
relation: "manyToOne",
|
|
215
451
|
target: RELEASE_MODEL_UID,
|
|
216
452
|
inversedBy: "actions"
|
|
453
|
+
},
|
|
454
|
+
isEntryValid: {
|
|
455
|
+
type: "boolean"
|
|
217
456
|
}
|
|
218
457
|
}
|
|
219
458
|
};
|
|
@@ -224,9 +463,6 @@ const contentTypes = {
|
|
|
224
463
|
release: release$1,
|
|
225
464
|
"release-action": releaseAction$1
|
|
226
465
|
};
|
|
227
|
-
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
228
|
-
return strapi2.plugin("content-releases").service(name);
|
|
229
|
-
};
|
|
230
466
|
const getGroupName = (queryValue) => {
|
|
231
467
|
switch (queryValue) {
|
|
232
468
|
case "contentType":
|
|
@@ -239,448 +475,563 @@ const getGroupName = (queryValue) => {
|
|
|
239
475
|
return "contentType.displayName";
|
|
240
476
|
}
|
|
241
477
|
};
|
|
242
|
-
const createReleaseService = ({ strapi: strapi2 }) =>
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
validateScheduledAtIsLaterThanNow
|
|
249
|
-
} = getService("release-validation", { strapi: strapi2 });
|
|
250
|
-
await Promise.all([
|
|
251
|
-
validatePendingReleasesLimit(),
|
|
252
|
-
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
253
|
-
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
254
|
-
]);
|
|
255
|
-
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
256
|
-
data: releaseWithCreatorFields
|
|
257
|
-
});
|
|
258
|
-
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
|
|
259
|
-
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
260
|
-
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
261
|
-
}
|
|
262
|
-
return release2;
|
|
263
|
-
},
|
|
264
|
-
async findOne(id, query = {}) {
|
|
265
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
266
|
-
...query
|
|
478
|
+
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
479
|
+
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
480
|
+
strapi2.eventHub.emit(event, {
|
|
481
|
+
isPublished,
|
|
482
|
+
error,
|
|
483
|
+
release: release2
|
|
267
484
|
});
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
485
|
+
};
|
|
486
|
+
return {
|
|
487
|
+
async create(releaseData, { user }) {
|
|
488
|
+
const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
|
|
489
|
+
const {
|
|
490
|
+
validatePendingReleasesLimit,
|
|
491
|
+
validateUniqueNameForPendingRelease,
|
|
492
|
+
validateScheduledAtIsLaterThanNow
|
|
493
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
494
|
+
await Promise.all([
|
|
495
|
+
validatePendingReleasesLimit(),
|
|
496
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
497
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
498
|
+
]);
|
|
499
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
500
|
+
data: {
|
|
501
|
+
...releaseWithCreatorFields,
|
|
502
|
+
status: "empty"
|
|
277
503
|
}
|
|
504
|
+
});
|
|
505
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
|
|
506
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
507
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
278
508
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
509
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
510
|
+
return release2;
|
|
511
|
+
},
|
|
512
|
+
async findOne(id, query = {}) {
|
|
513
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
514
|
+
...query
|
|
515
|
+
});
|
|
516
|
+
return release2;
|
|
517
|
+
},
|
|
518
|
+
findPage(query) {
|
|
519
|
+
return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
|
|
520
|
+
...query,
|
|
521
|
+
populate: {
|
|
522
|
+
actions: {
|
|
523
|
+
// @ts-expect-error Ignore missing properties
|
|
524
|
+
count: true
|
|
525
|
+
}
|
|
290
526
|
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
527
|
+
});
|
|
528
|
+
},
|
|
529
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
530
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
531
|
+
where: {
|
|
532
|
+
actions: {
|
|
296
533
|
target_type: contentTypeUid,
|
|
297
534
|
target_id: entryId
|
|
535
|
+
},
|
|
536
|
+
releasedAt: {
|
|
537
|
+
$null: true
|
|
298
538
|
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
return releases.map((release2) => {
|
|
303
|
-
if (release2.actions?.length) {
|
|
304
|
-
const [actionForEntry] = release2.actions;
|
|
305
|
-
delete release2.actions;
|
|
306
|
-
return {
|
|
307
|
-
...release2,
|
|
308
|
-
action: actionForEntry
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
return release2;
|
|
312
|
-
});
|
|
313
|
-
},
|
|
314
|
-
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
315
|
-
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
316
|
-
where: {
|
|
317
|
-
releasedAt: {
|
|
318
|
-
$null: true
|
|
319
539
|
},
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
327
|
-
where: {
|
|
328
|
-
$or: [
|
|
329
|
-
{
|
|
330
|
-
id: {
|
|
331
|
-
$notIn: releasesRelated.map((release2) => release2.id)
|
|
540
|
+
populate: {
|
|
541
|
+
// Filter the action to get only the content type entry
|
|
542
|
+
actions: {
|
|
543
|
+
where: {
|
|
544
|
+
target_type: contentTypeUid,
|
|
545
|
+
target_id: entryId
|
|
332
546
|
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
return releases.map((release2) => {
|
|
551
|
+
if (release2.actions?.length) {
|
|
552
|
+
const [actionForEntry] = release2.actions;
|
|
553
|
+
delete release2.actions;
|
|
554
|
+
return {
|
|
555
|
+
...release2,
|
|
556
|
+
action: actionForEntry
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
return release2;
|
|
560
|
+
});
|
|
561
|
+
},
|
|
562
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
563
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
564
|
+
where: {
|
|
565
|
+
releasedAt: {
|
|
566
|
+
$null: true
|
|
333
567
|
},
|
|
334
|
-
{
|
|
335
|
-
|
|
568
|
+
actions: {
|
|
569
|
+
target_type: contentTypeUid,
|
|
570
|
+
target_id: entryId
|
|
336
571
|
}
|
|
337
|
-
],
|
|
338
|
-
releasedAt: {
|
|
339
|
-
$null: true
|
|
340
572
|
}
|
|
573
|
+
});
|
|
574
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
575
|
+
where: {
|
|
576
|
+
$or: [
|
|
577
|
+
{
|
|
578
|
+
id: {
|
|
579
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
actions: null
|
|
584
|
+
}
|
|
585
|
+
],
|
|
586
|
+
releasedAt: {
|
|
587
|
+
$null: true
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
return releases.map((release2) => {
|
|
592
|
+
if (release2.actions?.length) {
|
|
593
|
+
const [actionForEntry] = release2.actions;
|
|
594
|
+
delete release2.actions;
|
|
595
|
+
return {
|
|
596
|
+
...release2,
|
|
597
|
+
action: actionForEntry
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
return release2;
|
|
601
|
+
});
|
|
602
|
+
},
|
|
603
|
+
async update(id, releaseData, { user }) {
|
|
604
|
+
const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(
|
|
605
|
+
releaseData
|
|
606
|
+
);
|
|
607
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
608
|
+
"release-validation",
|
|
609
|
+
{ strapi: strapi2 }
|
|
610
|
+
);
|
|
611
|
+
await Promise.all([
|
|
612
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
613
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
614
|
+
]);
|
|
615
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
616
|
+
if (!release2) {
|
|
617
|
+
throw new errors.NotFoundError(`No release found for id ${id}`);
|
|
341
618
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
if (release2.actions?.length) {
|
|
345
|
-
const [actionForEntry] = release2.actions;
|
|
346
|
-
delete release2.actions;
|
|
347
|
-
return {
|
|
348
|
-
...release2,
|
|
349
|
-
action: actionForEntry
|
|
350
|
-
};
|
|
619
|
+
if (release2.releasedAt) {
|
|
620
|
+
throw new errors.ValidationError("Release already published");
|
|
351
621
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
367
|
-
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
368
|
-
*/
|
|
369
|
-
// @ts-expect-error see above
|
|
370
|
-
data: releaseWithCreatorFields
|
|
371
|
-
});
|
|
372
|
-
return updatedRelease;
|
|
373
|
-
},
|
|
374
|
-
async createAction(releaseId, action) {
|
|
375
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
376
|
-
strapi: strapi2
|
|
377
|
-
});
|
|
378
|
-
await Promise.all([
|
|
379
|
-
validateEntryContentType(action.entry.contentType),
|
|
380
|
-
validateUniqueEntry(releaseId, action)
|
|
381
|
-
]);
|
|
382
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
383
|
-
if (!release2) {
|
|
384
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
385
|
-
}
|
|
386
|
-
if (release2.releasedAt) {
|
|
387
|
-
throw new errors.ValidationError("Release already published");
|
|
388
|
-
}
|
|
389
|
-
const { entry, type } = action;
|
|
390
|
-
return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
391
|
-
data: {
|
|
392
|
-
type,
|
|
393
|
-
contentType: entry.contentType,
|
|
394
|
-
locale: entry.locale,
|
|
395
|
-
entry: {
|
|
396
|
-
id: entry.id,
|
|
397
|
-
__type: entry.contentType,
|
|
398
|
-
__pivot: { field: "entry" }
|
|
399
|
-
},
|
|
400
|
-
release: releaseId
|
|
401
|
-
},
|
|
402
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
403
|
-
});
|
|
404
|
-
},
|
|
405
|
-
async findActions(releaseId, query) {
|
|
406
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
407
|
-
fields: ["id"]
|
|
408
|
-
});
|
|
409
|
-
if (!release2) {
|
|
410
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
411
|
-
}
|
|
412
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
413
|
-
...query,
|
|
414
|
-
populate: {
|
|
415
|
-
entry: {
|
|
416
|
-
populate: "*"
|
|
622
|
+
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
623
|
+
/*
|
|
624
|
+
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
625
|
+
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
626
|
+
*/
|
|
627
|
+
// @ts-expect-error see above
|
|
628
|
+
data: releaseWithCreatorFields
|
|
629
|
+
});
|
|
630
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
631
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
632
|
+
if (releaseData.scheduledAt) {
|
|
633
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
634
|
+
} else if (release2.scheduledAt) {
|
|
635
|
+
schedulingService.cancel(id);
|
|
417
636
|
}
|
|
418
|
-
},
|
|
419
|
-
filters: {
|
|
420
|
-
release: releaseId
|
|
421
637
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
638
|
+
this.updateReleaseStatus(id);
|
|
639
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
640
|
+
return updatedRelease;
|
|
641
|
+
},
|
|
642
|
+
async createAction(releaseId, action) {
|
|
643
|
+
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
644
|
+
strapi: strapi2
|
|
645
|
+
});
|
|
646
|
+
await Promise.all([
|
|
647
|
+
validateEntryContentType(action.entry.contentType),
|
|
648
|
+
validateUniqueEntry(releaseId, action)
|
|
649
|
+
]);
|
|
650
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
651
|
+
if (!release2) {
|
|
652
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
431
653
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
654
|
+
if (release2.releasedAt) {
|
|
655
|
+
throw new errors.ValidationError("Release already published");
|
|
656
|
+
}
|
|
657
|
+
const { entry, type } = action;
|
|
658
|
+
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
659
|
+
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
660
|
+
const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
661
|
+
data: {
|
|
662
|
+
type,
|
|
663
|
+
contentType: entry.contentType,
|
|
664
|
+
locale: entry.locale,
|
|
665
|
+
isEntryValid,
|
|
666
|
+
entry: {
|
|
667
|
+
id: entry.id,
|
|
668
|
+
__type: entry.contentType,
|
|
669
|
+
__pivot: { field: "entry" }
|
|
670
|
+
},
|
|
671
|
+
release: releaseId
|
|
672
|
+
},
|
|
673
|
+
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
674
|
+
});
|
|
675
|
+
this.updateReleaseStatus(releaseId);
|
|
676
|
+
return releaseAction2;
|
|
677
|
+
},
|
|
678
|
+
async findActions(releaseId, query) {
|
|
679
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
680
|
+
fields: ["id"]
|
|
681
|
+
});
|
|
682
|
+
if (!release2) {
|
|
683
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
684
|
+
}
|
|
685
|
+
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
686
|
+
...query,
|
|
687
|
+
populate: {
|
|
688
|
+
entry: {
|
|
689
|
+
populate: "*"
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
filters: {
|
|
693
|
+
release: releaseId
|
|
447
694
|
}
|
|
448
|
-
};
|
|
449
|
-
});
|
|
450
|
-
const groupName = getGroupName(groupBy);
|
|
451
|
-
return _.groupBy(groupName)(formattedData);
|
|
452
|
-
},
|
|
453
|
-
async getLocalesDataForActions() {
|
|
454
|
-
if (!strapi2.plugin("i18n")) {
|
|
455
|
-
return {};
|
|
456
|
-
}
|
|
457
|
-
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
458
|
-
return allLocales.reduce((acc, locale) => {
|
|
459
|
-
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
460
|
-
return acc;
|
|
461
|
-
}, {});
|
|
462
|
-
},
|
|
463
|
-
async getContentTypesDataForActions(contentTypesUids) {
|
|
464
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
465
|
-
const contentTypesData = {};
|
|
466
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
467
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
468
|
-
uid: contentTypeUid
|
|
469
695
|
});
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
696
|
+
},
|
|
697
|
+
async countActions(query) {
|
|
698
|
+
return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
|
|
699
|
+
},
|
|
700
|
+
async groupActions(actions, groupBy) {
|
|
701
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
702
|
+
if (!acc.includes(action.contentType)) {
|
|
703
|
+
acc.push(action.contentType);
|
|
704
|
+
}
|
|
705
|
+
return acc;
|
|
706
|
+
}, []);
|
|
707
|
+
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
|
|
708
|
+
contentTypeUids
|
|
709
|
+
);
|
|
710
|
+
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
711
|
+
const formattedData = actions.map((action) => {
|
|
712
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
713
|
+
return {
|
|
714
|
+
...action,
|
|
715
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
716
|
+
contentType: {
|
|
717
|
+
displayName,
|
|
718
|
+
mainFieldValue: action.entry[mainField],
|
|
719
|
+
uid: action.contentType
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
});
|
|
723
|
+
const groupName = getGroupName(groupBy);
|
|
724
|
+
return _.groupBy(groupName)(formattedData);
|
|
725
|
+
},
|
|
726
|
+
async getLocalesDataForActions() {
|
|
727
|
+
if (!strapi2.plugin("i18n")) {
|
|
728
|
+
return {};
|
|
481
729
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
(acc, contentTypeUid) => {
|
|
486
|
-
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
730
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
731
|
+
return allLocales.reduce((acc, locale) => {
|
|
732
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
487
733
|
return acc;
|
|
488
|
-
},
|
|
489
|
-
|
|
490
|
-
)
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
734
|
+
}, {});
|
|
735
|
+
},
|
|
736
|
+
async getContentTypesDataForActions(contentTypesUids) {
|
|
737
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
738
|
+
const contentTypesData = {};
|
|
739
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
740
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
741
|
+
uid: contentTypeUid
|
|
742
|
+
});
|
|
743
|
+
contentTypesData[contentTypeUid] = {
|
|
744
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
745
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
return contentTypesData;
|
|
749
|
+
},
|
|
750
|
+
getContentTypeModelsFromActions(actions) {
|
|
751
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
752
|
+
if (!acc.includes(action.contentType)) {
|
|
753
|
+
acc.push(action.contentType);
|
|
754
|
+
}
|
|
499
755
|
return acc;
|
|
500
|
-
},
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
756
|
+
}, []);
|
|
757
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
758
|
+
(acc, contentTypeUid) => {
|
|
759
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
760
|
+
return acc;
|
|
761
|
+
},
|
|
762
|
+
{}
|
|
763
|
+
);
|
|
764
|
+
return contentTypeModelsMap;
|
|
765
|
+
},
|
|
766
|
+
async getAllComponents() {
|
|
767
|
+
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
768
|
+
const components = await contentManagerComponentsService.findAllComponents();
|
|
769
|
+
const componentsMap = components.reduce(
|
|
770
|
+
(acc, component) => {
|
|
771
|
+
acc[component.uid] = component;
|
|
772
|
+
return acc;
|
|
773
|
+
},
|
|
774
|
+
{}
|
|
775
|
+
);
|
|
776
|
+
return componentsMap;
|
|
777
|
+
},
|
|
778
|
+
async delete(releaseId) {
|
|
779
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
780
|
+
populate: {
|
|
781
|
+
actions: {
|
|
782
|
+
fields: ["id"]
|
|
783
|
+
}
|
|
510
784
|
}
|
|
785
|
+
});
|
|
786
|
+
if (!release2) {
|
|
787
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
511
788
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
where: {
|
|
522
|
-
id: {
|
|
523
|
-
$in: release2.actions.map((action) => action.id)
|
|
789
|
+
if (release2.releasedAt) {
|
|
790
|
+
throw new errors.ValidationError("Release already published");
|
|
791
|
+
}
|
|
792
|
+
await strapi2.db.transaction(async () => {
|
|
793
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
794
|
+
where: {
|
|
795
|
+
id: {
|
|
796
|
+
$in: release2.actions.map((action) => action.id)
|
|
797
|
+
}
|
|
524
798
|
}
|
|
525
|
-
}
|
|
799
|
+
});
|
|
800
|
+
await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
|
526
801
|
});
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
{
|
|
536
|
-
|
|
537
|
-
|
|
802
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
|
|
803
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
804
|
+
await schedulingService.cancel(release2.id);
|
|
805
|
+
}
|
|
806
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
807
|
+
return release2;
|
|
808
|
+
},
|
|
809
|
+
async publish(releaseId) {
|
|
810
|
+
try {
|
|
811
|
+
const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
|
|
812
|
+
RELEASE_MODEL_UID,
|
|
813
|
+
releaseId,
|
|
814
|
+
{
|
|
538
815
|
populate: {
|
|
539
|
-
|
|
540
|
-
|
|
816
|
+
actions: {
|
|
817
|
+
populate: {
|
|
818
|
+
entry: {
|
|
819
|
+
fields: ["id"]
|
|
820
|
+
}
|
|
821
|
+
}
|
|
541
822
|
}
|
|
542
823
|
}
|
|
543
824
|
}
|
|
825
|
+
);
|
|
826
|
+
if (!releaseWithPopulatedActionEntries) {
|
|
827
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
544
828
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
if (!releaseWithPopulatedActionEntries) {
|
|
548
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
549
|
-
}
|
|
550
|
-
if (releaseWithPopulatedActionEntries.releasedAt) {
|
|
551
|
-
throw new errors.ValidationError("Release already published");
|
|
552
|
-
}
|
|
553
|
-
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
554
|
-
throw new errors.ValidationError("No entries to publish");
|
|
555
|
-
}
|
|
556
|
-
const collectionTypeActions = {};
|
|
557
|
-
const singleTypeActions = [];
|
|
558
|
-
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
559
|
-
const contentTypeUid = action.contentType;
|
|
560
|
-
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
561
|
-
if (!collectionTypeActions[contentTypeUid]) {
|
|
562
|
-
collectionTypeActions[contentTypeUid] = {
|
|
563
|
-
entriestoPublishIds: [],
|
|
564
|
-
entriesToUnpublishIds: []
|
|
565
|
-
};
|
|
829
|
+
if (releaseWithPopulatedActionEntries.releasedAt) {
|
|
830
|
+
throw new errors.ValidationError("Release already published");
|
|
566
831
|
}
|
|
567
|
-
if (
|
|
568
|
-
|
|
569
|
-
} else {
|
|
570
|
-
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
832
|
+
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
833
|
+
throw new errors.ValidationError("No entries to publish");
|
|
571
834
|
}
|
|
572
|
-
|
|
573
|
-
singleTypeActions
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
await entityManagerService.publish(entry, uid);
|
|
835
|
+
const collectionTypeActions = {};
|
|
836
|
+
const singleTypeActions = [];
|
|
837
|
+
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
838
|
+
const contentTypeUid = action.contentType;
|
|
839
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
840
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
841
|
+
collectionTypeActions[contentTypeUid] = {
|
|
842
|
+
entriestoPublishIds: [],
|
|
843
|
+
entriesToUnpublishIds: []
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
if (action.type === "publish") {
|
|
847
|
+
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
848
|
+
} else {
|
|
849
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
850
|
+
}
|
|
589
851
|
} else {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
;
|
|
595
|
-
else {
|
|
596
|
-
throw error;
|
|
852
|
+
singleTypeActions.push({
|
|
853
|
+
uid: contentTypeUid,
|
|
854
|
+
action: action.type,
|
|
855
|
+
id: action.entry.id
|
|
856
|
+
});
|
|
597
857
|
}
|
|
598
858
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
859
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
860
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
861
|
+
await strapi2.db.transaction(async () => {
|
|
862
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
863
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
864
|
+
const entry = await strapi2.entityService.findOne(uid, id, { populate });
|
|
865
|
+
try {
|
|
866
|
+
if (action === "publish") {
|
|
867
|
+
await entityManagerService.publish(entry, uid);
|
|
868
|
+
} else {
|
|
869
|
+
await entityManagerService.unpublish(entry, uid);
|
|
609
870
|
}
|
|
610
|
-
}
|
|
611
|
-
|
|
871
|
+
} catch (error) {
|
|
872
|
+
if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
|
|
873
|
+
} else {
|
|
874
|
+
throw error;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
612
877
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
878
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
879
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
880
|
+
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
881
|
+
const entriesToPublish = await strapi2.entityService.findMany(
|
|
882
|
+
contentTypeUid,
|
|
883
|
+
{
|
|
884
|
+
filters: {
|
|
885
|
+
id: {
|
|
886
|
+
$in: entriestoPublishIds
|
|
887
|
+
}
|
|
888
|
+
},
|
|
889
|
+
populate
|
|
620
890
|
}
|
|
621
|
-
|
|
622
|
-
|
|
891
|
+
);
|
|
892
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(
|
|
893
|
+
contentTypeUid,
|
|
894
|
+
{
|
|
895
|
+
filters: {
|
|
896
|
+
id: {
|
|
897
|
+
$in: entriesToUnpublishIds
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
populate
|
|
901
|
+
}
|
|
902
|
+
);
|
|
903
|
+
if (entriesToPublish.length > 0) {
|
|
904
|
+
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
905
|
+
}
|
|
906
|
+
if (entriesToUnpublish.length > 0) {
|
|
907
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
|
|
908
|
+
}
|
|
623
909
|
}
|
|
624
|
-
);
|
|
625
|
-
|
|
626
|
-
|
|
910
|
+
});
|
|
911
|
+
const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
|
|
912
|
+
data: {
|
|
913
|
+
/*
|
|
914
|
+
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
|
|
915
|
+
*/
|
|
916
|
+
// @ts-expect-error see above
|
|
917
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
918
|
+
},
|
|
919
|
+
populate: {
|
|
920
|
+
actions: {
|
|
921
|
+
// @ts-expect-error is not expecting count but it is working
|
|
922
|
+
count: true
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
927
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
928
|
+
isPublished: true,
|
|
929
|
+
release: release2
|
|
930
|
+
});
|
|
627
931
|
}
|
|
628
|
-
|
|
629
|
-
|
|
932
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
933
|
+
return release2;
|
|
934
|
+
} catch (error) {
|
|
935
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
936
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
937
|
+
isPublished: false,
|
|
938
|
+
error
|
|
939
|
+
});
|
|
630
940
|
}
|
|
941
|
+
strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
942
|
+
where: { id: releaseId },
|
|
943
|
+
data: {
|
|
944
|
+
status: "failed"
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
throw error;
|
|
631
948
|
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
949
|
+
},
|
|
950
|
+
async updateAction(actionId, releaseId, update) {
|
|
951
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
952
|
+
where: {
|
|
953
|
+
id: actionId,
|
|
954
|
+
release: {
|
|
955
|
+
id: releaseId,
|
|
956
|
+
releasedAt: {
|
|
957
|
+
$null: true
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
data: update
|
|
962
|
+
});
|
|
963
|
+
if (!updatedAction) {
|
|
964
|
+
throw new errors.NotFoundError(
|
|
965
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
966
|
+
);
|
|
640
967
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
968
|
+
return updatedAction;
|
|
969
|
+
},
|
|
970
|
+
async deleteAction(actionId, releaseId) {
|
|
971
|
+
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
972
|
+
where: {
|
|
973
|
+
id: actionId,
|
|
974
|
+
release: {
|
|
975
|
+
id: releaseId,
|
|
976
|
+
releasedAt: {
|
|
977
|
+
$null: true
|
|
978
|
+
}
|
|
652
979
|
}
|
|
653
980
|
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
);
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
981
|
+
});
|
|
982
|
+
if (!deletedAction) {
|
|
983
|
+
throw new errors.NotFoundError(
|
|
984
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
this.updateReleaseStatus(releaseId);
|
|
988
|
+
return deletedAction;
|
|
989
|
+
},
|
|
990
|
+
async updateReleaseStatus(releaseId) {
|
|
991
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
992
|
+
this.countActions({
|
|
993
|
+
filters: {
|
|
994
|
+
release: releaseId
|
|
995
|
+
}
|
|
996
|
+
}),
|
|
997
|
+
this.countActions({
|
|
998
|
+
filters: {
|
|
999
|
+
release: releaseId,
|
|
1000
|
+
isEntryValid: false
|
|
672
1001
|
}
|
|
1002
|
+
})
|
|
1003
|
+
]);
|
|
1004
|
+
if (totalActions > 0) {
|
|
1005
|
+
if (invalidActions > 0) {
|
|
1006
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1007
|
+
where: {
|
|
1008
|
+
id: releaseId
|
|
1009
|
+
},
|
|
1010
|
+
data: {
|
|
1011
|
+
status: "blocked"
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
673
1014
|
}
|
|
1015
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1016
|
+
where: {
|
|
1017
|
+
id: releaseId
|
|
1018
|
+
},
|
|
1019
|
+
data: {
|
|
1020
|
+
status: "ready"
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
674
1023
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1024
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1025
|
+
where: {
|
|
1026
|
+
id: releaseId
|
|
1027
|
+
},
|
|
1028
|
+
data: {
|
|
1029
|
+
status: "empty"
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
680
1032
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
});
|
|
1033
|
+
};
|
|
1034
|
+
};
|
|
684
1035
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
685
1036
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
686
1037
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
@@ -725,13 +1076,14 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
725
1076
|
throw new errors.ValidationError("You have reached the maximum number of pending releases");
|
|
726
1077
|
}
|
|
727
1078
|
},
|
|
728
|
-
async validateUniqueNameForPendingRelease(name) {
|
|
1079
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
729
1080
|
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
730
1081
|
filters: {
|
|
731
1082
|
releasedAt: {
|
|
732
1083
|
$null: true
|
|
733
1084
|
},
|
|
734
|
-
name
|
|
1085
|
+
name,
|
|
1086
|
+
...id && { id: { $ne: id } }
|
|
735
1087
|
}
|
|
736
1088
|
});
|
|
737
1089
|
const isNameUnique = pendingReleases.length === 0;
|
|
@@ -772,6 +1124,28 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
|
772
1124
|
scheduledJobs.delete(releaseId);
|
|
773
1125
|
}
|
|
774
1126
|
return scheduledJobs;
|
|
1127
|
+
},
|
|
1128
|
+
getAll() {
|
|
1129
|
+
return scheduledJobs;
|
|
1130
|
+
},
|
|
1131
|
+
/**
|
|
1132
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
1133
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
1134
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
1135
|
+
*/
|
|
1136
|
+
async syncFromDatabase() {
|
|
1137
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1138
|
+
where: {
|
|
1139
|
+
scheduledAt: {
|
|
1140
|
+
$gte: /* @__PURE__ */ new Date()
|
|
1141
|
+
},
|
|
1142
|
+
releasedAt: null
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
for (const release2 of releases) {
|
|
1146
|
+
this.set(release2.id, release2.scheduledAt);
|
|
1147
|
+
}
|
|
1148
|
+
return scheduledJobs;
|
|
775
1149
|
}
|
|
776
1150
|
};
|
|
777
1151
|
};
|
|
@@ -782,8 +1156,23 @@ const services = {
|
|
|
782
1156
|
};
|
|
783
1157
|
const RELEASE_SCHEMA = yup.object().shape({
|
|
784
1158
|
name: yup.string().trim().required(),
|
|
785
|
-
|
|
786
|
-
|
|
1159
|
+
scheduledAt: yup.string().nullable(),
|
|
1160
|
+
isScheduled: yup.boolean().optional(),
|
|
1161
|
+
time: yup.string().when("isScheduled", {
|
|
1162
|
+
is: true,
|
|
1163
|
+
then: yup.string().trim().required(),
|
|
1164
|
+
otherwise: yup.string().nullable()
|
|
1165
|
+
}),
|
|
1166
|
+
timezone: yup.string().when("isScheduled", {
|
|
1167
|
+
is: true,
|
|
1168
|
+
then: yup.string().required().nullable(),
|
|
1169
|
+
otherwise: yup.string().nullable()
|
|
1170
|
+
}),
|
|
1171
|
+
date: yup.string().when("isScheduled", {
|
|
1172
|
+
is: true,
|
|
1173
|
+
then: yup.string().required().nullable(),
|
|
1174
|
+
otherwise: yup.string().nullable()
|
|
1175
|
+
})
|
|
787
1176
|
}).required().noUnknown();
|
|
788
1177
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
789
1178
|
const releaseController = {
|
|
@@ -816,7 +1205,12 @@ const releaseController = {
|
|
|
816
1205
|
}
|
|
817
1206
|
};
|
|
818
1207
|
});
|
|
819
|
-
|
|
1208
|
+
const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
|
|
1209
|
+
where: {
|
|
1210
|
+
releasedAt: null
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
820
1214
|
}
|
|
821
1215
|
},
|
|
822
1216
|
async findOne(ctx) {
|
|
@@ -1179,6 +1573,7 @@ const getPlugin = () => {
|
|
|
1179
1573
|
return {
|
|
1180
1574
|
register,
|
|
1181
1575
|
bootstrap,
|
|
1576
|
+
destroy,
|
|
1182
1577
|
contentTypes,
|
|
1183
1578
|
services,
|
|
1184
1579
|
controllers,
|