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