@strapi/content-releases 0.0.0-experimental.f7b9b47085e387e97f990d8695971b51d7f7149a → 0.0.0-experimental.fc1ac2acd58c8a5a858679956b6d102ac5ee4011
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-0Er6xxcq.mjs +1358 -0
- package/dist/_chunks/App-0Er6xxcq.mjs.map +1 -0
- package/dist/_chunks/App-C768ulk4.js +1381 -0
- package/dist/_chunks/App-C768ulk4.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-e98d8b57.mjs → en-WuuhP6Bn.mjs} +33 -8
- package/dist/_chunks/en-WuuhP6Bn.mjs.map +1 -0
- package/dist/_chunks/{en-13576ce2.js → en-gcJJ5htG.js} +33 -8
- package/dist/_chunks/en-gcJJ5htG.js.map +1 -0
- package/dist/_chunks/{index-66d129ac.js → index-BLSMpbpZ.js} +258 -68
- package/dist/_chunks/index-BLSMpbpZ.js.map +1 -0
- package/dist/_chunks/{index-937f8179.mjs → index-fJx1up7m.mjs} +273 -83
- package/dist/_chunks/index-fJx1up7m.mjs.map +1 -0
- package/dist/admin/index.js +2 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +3 -2
- package/dist/admin/index.mjs.map +1 -1
- package/dist/admin/src/components/CMReleasesContainer.d.ts +1 -0
- package/dist/admin/src/components/RelativeTime.d.ts +28 -0
- package/dist/admin/src/components/ReleaseActionMenu.d.ts +26 -0
- package/dist/admin/src/components/ReleaseActionOptions.d.ts +9 -0
- package/dist/admin/src/components/ReleaseModal.d.ts +16 -0
- package/dist/admin/src/constants.d.ts +58 -0
- package/dist/admin/src/index.d.ts +3 -0
- package/dist/admin/src/pages/App.d.ts +1 -0
- package/dist/admin/src/pages/PurchaseContentReleases.d.ts +2 -0
- package/dist/admin/src/pages/ReleaseDetailsPage.d.ts +2 -0
- package/dist/admin/src/pages/ReleasesPage.d.ts +8 -0
- package/dist/admin/src/pages/tests/mockReleaseDetailsPageData.d.ts +181 -0
- package/dist/admin/src/pages/tests/mockReleasesPageData.d.ts +39 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/admin/src/services/axios.d.ts +29 -0
- package/dist/admin/src/services/release.d.ts +369 -0
- package/dist/admin/src/store/hooks.d.ts +7 -0
- package/dist/admin/src/utils/time.d.ts +1 -0
- package/dist/server/index.js +1008 -298
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1006 -299
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/bootstrap.d.ts.map +1 -0
- package/dist/server/src/constants.d.ts +12 -0
- package/dist/server/src/constants.d.ts.map +1 -0
- package/dist/server/src/content-types/index.d.ts +99 -0
- package/dist/server/src/content-types/index.d.ts.map +1 -0
- package/dist/server/src/content-types/release/index.d.ts +48 -0
- package/dist/server/src/content-types/release/index.d.ts.map +1 -0
- package/dist/server/src/content-types/release/schema.d.ts +47 -0
- package/dist/server/src/content-types/release/schema.d.ts.map +1 -0
- package/dist/server/src/content-types/release-action/index.d.ts +50 -0
- package/dist/server/src/content-types/release-action/index.d.ts.map +1 -0
- package/dist/server/src/content-types/release-action/schema.d.ts +49 -0
- package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -0
- package/dist/server/src/controllers/index.d.ts +18 -0
- package/dist/server/src/controllers/index.d.ts.map +1 -0
- package/dist/server/src/controllers/release-action.d.ts +9 -0
- package/dist/server/src/controllers/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/release.d.ts +11 -0
- package/dist/server/src/controllers/release.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release-action.d.ts +3 -0
- package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release.d.ts +2 -0
- package/dist/server/src/controllers/validation/release.d.ts.map +1 -0
- package/dist/server/src/destroy.d.ts +5 -0
- package/dist/server/src/destroy.d.ts.map +1 -0
- package/dist/server/src/index.d.ts +3838 -0
- package/dist/server/src/index.d.ts.map +1 -0
- package/dist/server/src/migrations/index.d.ts +10 -0
- package/dist/server/src/migrations/index.d.ts.map +1 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/register.d.ts.map +1 -0
- package/dist/server/src/routes/index.d.ts +35 -0
- package/dist/server/src/routes/index.d.ts.map +1 -0
- package/dist/server/src/routes/release-action.d.ts +18 -0
- package/dist/server/src/routes/release-action.d.ts.map +1 -0
- package/dist/server/src/routes/release.d.ts +18 -0
- package/dist/server/src/routes/release.d.ts.map +1 -0
- package/dist/server/src/services/index.d.ts +3572 -0
- package/dist/server/src/services/index.d.ts.map +1 -0
- package/dist/server/src/services/release.d.ts +1812 -0
- package/dist/server/src/services/release.d.ts.map +1 -0
- package/dist/server/src/services/scheduling.d.ts +18 -0
- package/dist/server/src/services/scheduling.d.ts.map +1 -0
- package/dist/server/src/services/validation.d.ts +14 -0
- package/dist/server/src/services/validation.d.ts.map +1 -0
- package/dist/server/src/utils/index.d.ts +18 -0
- package/dist/server/src/utils/index.d.ts.map +1 -0
- package/dist/shared/contracts/release-actions.d.ts +105 -0
- package/dist/shared/contracts/release-actions.d.ts.map +1 -0
- package/dist/shared/contracts/releases.d.ts +166 -0
- package/dist/shared/contracts/releases.d.ts.map +1 -0
- package/dist/shared/types.d.ts +24 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/validation-schemas.d.ts +2 -0
- package/dist/shared/validation-schemas.d.ts.map +1 -0
- package/package.json +20 -22
- package/dist/_chunks/App-a4843fda.mjs +0 -855
- package/dist/_chunks/App-a4843fda.mjs.map +0 -1
- package/dist/_chunks/App-f2cafd81.js +0 -877
- package/dist/_chunks/App-f2cafd81.js.map +0 -1
- package/dist/_chunks/en-13576ce2.js.map +0 -1
- package/dist/_chunks/en-e98d8b57.mjs.map +0 -1
- package/dist/_chunks/index-66d129ac.js.map +0 -1
- package/dist/_chunks/index-937f8179.mjs.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const utils = require("@strapi/utils");
|
|
3
|
+
const isEqual = require("lodash/isEqual");
|
|
4
|
+
const lodash = require("lodash");
|
|
5
|
+
const _ = require("lodash/fp");
|
|
6
|
+
const nodeSchedule = require("node-schedule");
|
|
3
7
|
const yup = require("yup");
|
|
8
|
+
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
4
9
|
function _interopNamespace(e) {
|
|
5
10
|
if (e && e.__esModule)
|
|
6
11
|
return e;
|
|
@@ -19,6 +24,8 @@ function _interopNamespace(e) {
|
|
|
19
24
|
n.default = e;
|
|
20
25
|
return Object.freeze(n);
|
|
21
26
|
}
|
|
27
|
+
const isEqual__default = /* @__PURE__ */ _interopDefault(isEqual);
|
|
28
|
+
const ___default = /* @__PURE__ */ _interopDefault(_);
|
|
22
29
|
const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
|
|
23
30
|
const RELEASE_MODEL_UID = "plugin::content-releases.release";
|
|
24
31
|
const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
|
|
@@ -66,10 +73,289 @@ const ACTIONS = [
|
|
|
66
73
|
pluginName: "content-releases"
|
|
67
74
|
}
|
|
68
75
|
];
|
|
69
|
-
const
|
|
76
|
+
const ALLOWED_WEBHOOK_EVENTS = {
|
|
77
|
+
RELEASES_PUBLISH: "releases.publish"
|
|
78
|
+
};
|
|
79
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
80
|
+
return strapi2.plugin("content-releases").service(name);
|
|
81
|
+
};
|
|
82
|
+
const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
83
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
84
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
85
|
+
const entry = await strapi2.entityService.findOne(contentTypeUid, entryId, { populate });
|
|
86
|
+
return entry;
|
|
87
|
+
};
|
|
88
|
+
const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
89
|
+
try {
|
|
90
|
+
await strapi2.entityValidator.validateEntityCreation(
|
|
91
|
+
strapi2.getModel(contentTypeUid),
|
|
92
|
+
entry,
|
|
93
|
+
void 0,
|
|
94
|
+
// @ts-expect-error - FIXME: entity here is unnecessary
|
|
95
|
+
entry
|
|
96
|
+
);
|
|
97
|
+
return true;
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
103
|
+
const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
|
|
104
|
+
if (deletedContentTypes.length) {
|
|
105
|
+
await utils.async.map(deletedContentTypes, async (deletedContentTypeUID) => {
|
|
106
|
+
return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function migrateIsValidAndStatusReleases() {
|
|
111
|
+
const releasesWithoutStatus = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
112
|
+
where: {
|
|
113
|
+
status: null,
|
|
114
|
+
releasedAt: null
|
|
115
|
+
},
|
|
116
|
+
populate: {
|
|
117
|
+
actions: {
|
|
118
|
+
populate: {
|
|
119
|
+
entry: true
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
utils.async.map(releasesWithoutStatus, async (release2) => {
|
|
125
|
+
const actions = release2.actions;
|
|
126
|
+
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
127
|
+
for (const action of notValidatedActions) {
|
|
128
|
+
if (action.entry) {
|
|
129
|
+
const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
|
|
130
|
+
strapi
|
|
131
|
+
});
|
|
132
|
+
if (populatedEntry) {
|
|
133
|
+
const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
|
|
134
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
135
|
+
where: {
|
|
136
|
+
id: action.id
|
|
137
|
+
},
|
|
138
|
+
data: {
|
|
139
|
+
isEntryValid
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
146
|
+
});
|
|
147
|
+
const publishedReleases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
148
|
+
where: {
|
|
149
|
+
status: null,
|
|
150
|
+
releasedAt: {
|
|
151
|
+
$notNull: true
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
utils.async.map(publishedReleases, async (release2) => {
|
|
156
|
+
return strapi.db.query(RELEASE_MODEL_UID).update({
|
|
157
|
+
where: {
|
|
158
|
+
id: release2.id
|
|
159
|
+
},
|
|
160
|
+
data: {
|
|
161
|
+
status: "done"
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
167
|
+
if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
|
|
168
|
+
const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes);
|
|
169
|
+
const releasesAffected = /* @__PURE__ */ new Set();
|
|
170
|
+
utils.async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
|
|
171
|
+
const oldContentType = oldContentTypes[contentTypeUID];
|
|
172
|
+
const contentType = contentTypes2[contentTypeUID];
|
|
173
|
+
if (!isEqual__default.default(oldContentType?.attributes, contentType?.attributes)) {
|
|
174
|
+
const actions = await strapi.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
175
|
+
where: {
|
|
176
|
+
contentType: contentTypeUID
|
|
177
|
+
},
|
|
178
|
+
populate: {
|
|
179
|
+
entry: true,
|
|
180
|
+
release: true
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
await utils.async.map(actions, async (action) => {
|
|
184
|
+
if (action.entry) {
|
|
185
|
+
const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
|
|
186
|
+
strapi
|
|
187
|
+
});
|
|
188
|
+
if (populatedEntry) {
|
|
189
|
+
const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
|
|
190
|
+
strapi
|
|
191
|
+
});
|
|
192
|
+
releasesAffected.add(action.release.id);
|
|
193
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
194
|
+
where: {
|
|
195
|
+
id: action.id
|
|
196
|
+
},
|
|
197
|
+
data: {
|
|
198
|
+
isEntryValid
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}).then(() => {
|
|
206
|
+
utils.async.map(releasesAffected, async (releaseId) => {
|
|
207
|
+
return getService("release", { strapi }).updateReleaseStatus(releaseId);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
70
212
|
const register = async ({ strapi: strapi2 }) => {
|
|
71
|
-
if (features
|
|
213
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
72
214
|
await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
|
215
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
219
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
220
|
+
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes);
|
|
221
|
+
strapi2.db.lifecycles.subscribe({
|
|
222
|
+
models: contentTypesWithDraftAndPublish,
|
|
223
|
+
async afterDelete(event) {
|
|
224
|
+
try {
|
|
225
|
+
const { model, result } = event;
|
|
226
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
227
|
+
const { id } = result;
|
|
228
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
229
|
+
where: {
|
|
230
|
+
actions: {
|
|
231
|
+
target_type: model.uid,
|
|
232
|
+
target_id: id
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
237
|
+
where: {
|
|
238
|
+
target_type: model.uid,
|
|
239
|
+
target_id: id
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
for (const release2 of releases) {
|
|
243
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
/**
|
|
251
|
+
* deleteMany hook doesn't return the deleted entries ids
|
|
252
|
+
* so we need to fetch them before deleting the entries to save the ids on our state
|
|
253
|
+
*/
|
|
254
|
+
async beforeDeleteMany(event) {
|
|
255
|
+
const { model, params } = event;
|
|
256
|
+
if (model.kind === "collectionType") {
|
|
257
|
+
const { where } = params;
|
|
258
|
+
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
259
|
+
event.state.entriesToDelete = entriesToDelete;
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
/**
|
|
263
|
+
* We delete the release actions related to deleted entries
|
|
264
|
+
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
265
|
+
*/
|
|
266
|
+
async afterDeleteMany(event) {
|
|
267
|
+
try {
|
|
268
|
+
const { model, state } = event;
|
|
269
|
+
const entriesToDelete = state.entriesToDelete;
|
|
270
|
+
if (entriesToDelete) {
|
|
271
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
272
|
+
where: {
|
|
273
|
+
actions: {
|
|
274
|
+
target_type: model.uid,
|
|
275
|
+
target_id: {
|
|
276
|
+
$in: entriesToDelete.map(
|
|
277
|
+
(entry) => entry.id
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
284
|
+
where: {
|
|
285
|
+
target_type: model.uid,
|
|
286
|
+
target_id: {
|
|
287
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
for (const release2 of releases) {
|
|
292
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} catch (error) {
|
|
296
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
297
|
+
error
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
async afterUpdate(event) {
|
|
302
|
+
try {
|
|
303
|
+
const { model, result } = event;
|
|
304
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
305
|
+
const isEntryValid = await getEntryValidStatus(
|
|
306
|
+
model.uid,
|
|
307
|
+
result,
|
|
308
|
+
{
|
|
309
|
+
strapi: strapi2
|
|
310
|
+
}
|
|
311
|
+
);
|
|
312
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
313
|
+
where: {
|
|
314
|
+
target_type: model.uid,
|
|
315
|
+
target_id: result.id
|
|
316
|
+
},
|
|
317
|
+
data: {
|
|
318
|
+
isEntryValid
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
322
|
+
where: {
|
|
323
|
+
actions: {
|
|
324
|
+
target_type: model.uid,
|
|
325
|
+
target_id: result.id
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
for (const release2 of releases) {
|
|
330
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
} catch (error) {
|
|
334
|
+
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
339
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
340
|
+
strapi2.log.error(
|
|
341
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
342
|
+
);
|
|
343
|
+
throw err;
|
|
344
|
+
});
|
|
345
|
+
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
346
|
+
strapi2.webhookStore.addAllowedEvent(key, value);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
const destroy = async ({ strapi: strapi2 }) => {
|
|
352
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
353
|
+
const scheduledJobs = getService("scheduling", {
|
|
354
|
+
strapi: strapi2
|
|
355
|
+
}).getAll();
|
|
356
|
+
for (const [, job] of scheduledJobs) {
|
|
357
|
+
job.cancel();
|
|
358
|
+
}
|
|
73
359
|
}
|
|
74
360
|
};
|
|
75
361
|
const schema$1 = {
|
|
@@ -98,6 +384,17 @@ const schema$1 = {
|
|
|
98
384
|
releasedAt: {
|
|
99
385
|
type: "datetime"
|
|
100
386
|
},
|
|
387
|
+
scheduledAt: {
|
|
388
|
+
type: "datetime"
|
|
389
|
+
},
|
|
390
|
+
timezone: {
|
|
391
|
+
type: "string"
|
|
392
|
+
},
|
|
393
|
+
status: {
|
|
394
|
+
type: "enumeration",
|
|
395
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
396
|
+
required: true
|
|
397
|
+
},
|
|
101
398
|
actions: {
|
|
102
399
|
type: "relation",
|
|
103
400
|
relation: "oneToMany",
|
|
@@ -142,11 +439,17 @@ const schema = {
|
|
|
142
439
|
type: "string",
|
|
143
440
|
required: true
|
|
144
441
|
},
|
|
442
|
+
locale: {
|
|
443
|
+
type: "string"
|
|
444
|
+
},
|
|
145
445
|
release: {
|
|
146
446
|
type: "relation",
|
|
147
447
|
relation: "manyToOne",
|
|
148
448
|
target: RELEASE_MODEL_UID,
|
|
149
449
|
inversedBy: "actions"
|
|
450
|
+
},
|
|
451
|
+
isEntryValid: {
|
|
452
|
+
type: "boolean"
|
|
150
453
|
}
|
|
151
454
|
}
|
|
152
455
|
};
|
|
@@ -157,290 +460,575 @@ const contentTypes = {
|
|
|
157
460
|
release: release$1,
|
|
158
461
|
"release-action": releaseAction$1
|
|
159
462
|
};
|
|
160
|
-
const
|
|
161
|
-
|
|
463
|
+
const getGroupName = (queryValue) => {
|
|
464
|
+
switch (queryValue) {
|
|
465
|
+
case "contentType":
|
|
466
|
+
return "contentType.displayName";
|
|
467
|
+
case "action":
|
|
468
|
+
return "type";
|
|
469
|
+
case "locale":
|
|
470
|
+
return ___default.default.getOr("No locale", "locale.name");
|
|
471
|
+
default:
|
|
472
|
+
return "contentType.displayName";
|
|
473
|
+
}
|
|
162
474
|
};
|
|
163
|
-
const createReleaseService = ({ strapi: strapi2 }) =>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
},
|
|
170
|
-
async findOne(id, query = {}) {
|
|
171
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
172
|
-
...query
|
|
475
|
+
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
476
|
+
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
477
|
+
strapi2.eventHub.emit(event, {
|
|
478
|
+
isPublished,
|
|
479
|
+
error,
|
|
480
|
+
release: release2
|
|
173
481
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
482
|
+
};
|
|
483
|
+
return {
|
|
484
|
+
async create(releaseData, { user }) {
|
|
485
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
|
|
486
|
+
const {
|
|
487
|
+
validatePendingReleasesLimit,
|
|
488
|
+
validateUniqueNameForPendingRelease,
|
|
489
|
+
validateScheduledAtIsLaterThanNow
|
|
490
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
491
|
+
await Promise.all([
|
|
492
|
+
validatePendingReleasesLimit(),
|
|
493
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
494
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
495
|
+
]);
|
|
496
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
497
|
+
data: {
|
|
498
|
+
...releaseWithCreatorFields,
|
|
499
|
+
status: "empty"
|
|
183
500
|
}
|
|
501
|
+
});
|
|
502
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
|
|
503
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
504
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
184
505
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
506
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
507
|
+
return release2;
|
|
508
|
+
},
|
|
509
|
+
async findOne(id, query = {}) {
|
|
510
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
511
|
+
...query
|
|
512
|
+
});
|
|
513
|
+
return release2;
|
|
514
|
+
},
|
|
515
|
+
findPage(query) {
|
|
516
|
+
return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
|
|
517
|
+
...query,
|
|
518
|
+
populate: {
|
|
519
|
+
actions: {
|
|
520
|
+
// @ts-expect-error Ignore missing properties
|
|
521
|
+
count: true
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
},
|
|
526
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
527
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
528
|
+
where: {
|
|
529
|
+
actions: {
|
|
530
|
+
target_type: contentTypeUid,
|
|
531
|
+
target_id: entryId
|
|
532
|
+
},
|
|
533
|
+
releasedAt: {
|
|
534
|
+
$null: true
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
populate: {
|
|
538
|
+
// Filter the action to get only the content type entry
|
|
539
|
+
actions: {
|
|
540
|
+
where: {
|
|
204
541
|
target_type: contentTypeUid,
|
|
205
542
|
target_id: entryId
|
|
206
543
|
}
|
|
207
544
|
}
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
actions: null
|
|
211
545
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
546
|
+
});
|
|
547
|
+
return releases.map((release2) => {
|
|
548
|
+
if (release2.actions?.length) {
|
|
549
|
+
const [actionForEntry] = release2.actions;
|
|
550
|
+
delete release2.actions;
|
|
551
|
+
return {
|
|
552
|
+
...release2,
|
|
553
|
+
action: actionForEntry
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
return release2;
|
|
557
|
+
});
|
|
558
|
+
},
|
|
559
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
560
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
561
|
+
where: {
|
|
562
|
+
releasedAt: {
|
|
563
|
+
$null: true
|
|
564
|
+
},
|
|
565
|
+
actions: {
|
|
566
|
+
target_type: contentTypeUid,
|
|
567
|
+
target_id: entryId
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
217
572
|
where: {
|
|
218
|
-
|
|
219
|
-
|
|
573
|
+
$or: [
|
|
574
|
+
{
|
|
575
|
+
id: {
|
|
576
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
actions: null
|
|
581
|
+
}
|
|
582
|
+
],
|
|
583
|
+
releasedAt: {
|
|
584
|
+
$null: true
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
return releases.map((release2) => {
|
|
589
|
+
if (release2.actions?.length) {
|
|
590
|
+
const [actionForEntry] = release2.actions;
|
|
591
|
+
delete release2.actions;
|
|
592
|
+
return {
|
|
593
|
+
...release2,
|
|
594
|
+
action: actionForEntry
|
|
595
|
+
};
|
|
220
596
|
}
|
|
597
|
+
return release2;
|
|
598
|
+
});
|
|
599
|
+
},
|
|
600
|
+
async update(id, releaseData, { user }) {
|
|
601
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(
|
|
602
|
+
releaseData
|
|
603
|
+
);
|
|
604
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
605
|
+
"release-validation",
|
|
606
|
+
{ strapi: strapi2 }
|
|
607
|
+
);
|
|
608
|
+
await Promise.all([
|
|
609
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
610
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
611
|
+
]);
|
|
612
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
613
|
+
if (!release2) {
|
|
614
|
+
throw new utils.errors.NotFoundError(`No release found for id ${id}`);
|
|
221
615
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
616
|
+
if (release2.releasedAt) {
|
|
617
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
618
|
+
}
|
|
619
|
+
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
620
|
+
/*
|
|
621
|
+
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
622
|
+
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
623
|
+
*/
|
|
624
|
+
// @ts-expect-error see above
|
|
625
|
+
data: releaseWithCreatorFields
|
|
626
|
+
});
|
|
627
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
628
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
629
|
+
if (releaseData.scheduledAt) {
|
|
630
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
631
|
+
} else if (release2.scheduledAt) {
|
|
632
|
+
schedulingService.cancel(id);
|
|
228
633
|
}
|
|
229
|
-
},
|
|
230
|
-
populate: {
|
|
231
|
-
...populateAttachedAction
|
|
232
634
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
635
|
+
this.updateReleaseStatus(id);
|
|
636
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
637
|
+
return updatedRelease;
|
|
638
|
+
},
|
|
639
|
+
async createAction(releaseId, action) {
|
|
640
|
+
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
641
|
+
strapi: strapi2
|
|
642
|
+
});
|
|
643
|
+
await Promise.all([
|
|
644
|
+
validateEntryContentType(action.entry.contentType),
|
|
645
|
+
validateUniqueEntry(releaseId, action)
|
|
646
|
+
]);
|
|
647
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
648
|
+
if (!release2) {
|
|
649
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
242
650
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
async createAction(releaseId, action) {
|
|
262
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
263
|
-
strapi: strapi2
|
|
264
|
-
});
|
|
265
|
-
await Promise.all([
|
|
266
|
-
validateEntryContentType(action.entry.contentType),
|
|
267
|
-
validateUniqueEntry(releaseId, action)
|
|
268
|
-
]);
|
|
269
|
-
const { entry, type } = action;
|
|
270
|
-
return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
271
|
-
data: {
|
|
272
|
-
type,
|
|
273
|
-
contentType: entry.contentType,
|
|
274
|
-
entry: {
|
|
275
|
-
id: entry.id,
|
|
276
|
-
__type: entry.contentType,
|
|
277
|
-
__pivot: { field: "entry" }
|
|
651
|
+
if (release2.releasedAt) {
|
|
652
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
653
|
+
}
|
|
654
|
+
const { entry, type } = action;
|
|
655
|
+
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
656
|
+
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
657
|
+
const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
658
|
+
data: {
|
|
659
|
+
type,
|
|
660
|
+
contentType: entry.contentType,
|
|
661
|
+
locale: entry.locale,
|
|
662
|
+
isEntryValid,
|
|
663
|
+
entry: {
|
|
664
|
+
id: entry.id,
|
|
665
|
+
__type: entry.contentType,
|
|
666
|
+
__pivot: { field: "entry" }
|
|
667
|
+
},
|
|
668
|
+
release: releaseId
|
|
278
669
|
},
|
|
279
|
-
release:
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
...query,
|
|
291
|
-
populate: {
|
|
292
|
-
entry: true
|
|
293
|
-
},
|
|
294
|
-
filters: {
|
|
295
|
-
release: releaseId
|
|
670
|
+
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
671
|
+
});
|
|
672
|
+
this.updateReleaseStatus(releaseId);
|
|
673
|
+
return releaseAction2;
|
|
674
|
+
},
|
|
675
|
+
async findActions(releaseId, query) {
|
|
676
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
677
|
+
fields: ["id"]
|
|
678
|
+
});
|
|
679
|
+
if (!release2) {
|
|
680
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
296
681
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
{
|
|
682
|
+
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
683
|
+
...query,
|
|
684
|
+
populate: {
|
|
685
|
+
entry: {
|
|
686
|
+
populate: "*"
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
filters: {
|
|
306
690
|
release: releaseId
|
|
307
691
|
}
|
|
308
|
-
]
|
|
309
|
-
}).groupBy("content_type").execute();
|
|
310
|
-
return contentTypesFromReleaseActions.map(({ contentType: contentTypeUid }) => contentTypeUid);
|
|
311
|
-
},
|
|
312
|
-
async getContentTypesDataForActions(releaseId) {
|
|
313
|
-
const contentTypesUids = await this.getAllContentTypeUids(releaseId);
|
|
314
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
315
|
-
const contentTypesData = {};
|
|
316
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
317
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
318
|
-
uid: contentTypeUid
|
|
319
692
|
});
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
329
|
-
populate: {
|
|
330
|
-
actions: {
|
|
331
|
-
fields: ["id"]
|
|
693
|
+
},
|
|
694
|
+
async countActions(query) {
|
|
695
|
+
return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
|
|
696
|
+
},
|
|
697
|
+
async groupActions(actions, groupBy) {
|
|
698
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
699
|
+
if (!acc.includes(action.contentType)) {
|
|
700
|
+
acc.push(action.contentType);
|
|
332
701
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
702
|
+
return acc;
|
|
703
|
+
}, []);
|
|
704
|
+
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
|
|
705
|
+
contentTypeUids
|
|
706
|
+
);
|
|
707
|
+
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
708
|
+
const formattedData = actions.map((action) => {
|
|
709
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
710
|
+
return {
|
|
711
|
+
...action,
|
|
712
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
713
|
+
contentType: {
|
|
714
|
+
displayName,
|
|
715
|
+
mainFieldValue: action.entry[mainField],
|
|
716
|
+
uid: action.contentType
|
|
346
717
|
}
|
|
347
|
-
}
|
|
718
|
+
};
|
|
348
719
|
});
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
{
|
|
720
|
+
const groupName = getGroupName(groupBy);
|
|
721
|
+
return ___default.default.groupBy(groupName)(formattedData);
|
|
722
|
+
},
|
|
723
|
+
async getLocalesDataForActions() {
|
|
724
|
+
if (!strapi2.plugin("i18n")) {
|
|
725
|
+
return {};
|
|
726
|
+
}
|
|
727
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
728
|
+
return allLocales.reduce((acc, locale) => {
|
|
729
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
730
|
+
return acc;
|
|
731
|
+
}, {});
|
|
732
|
+
},
|
|
733
|
+
async getContentTypesDataForActions(contentTypesUids) {
|
|
734
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
735
|
+
const contentTypesData = {};
|
|
736
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
737
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
738
|
+
uid: contentTypeUid
|
|
739
|
+
});
|
|
740
|
+
contentTypesData[contentTypeUid] = {
|
|
741
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
742
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
return contentTypesData;
|
|
746
|
+
},
|
|
747
|
+
getContentTypeModelsFromActions(actions) {
|
|
748
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
749
|
+
if (!acc.includes(action.contentType)) {
|
|
750
|
+
acc.push(action.contentType);
|
|
751
|
+
}
|
|
752
|
+
return acc;
|
|
753
|
+
}, []);
|
|
754
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
755
|
+
(acc, contentTypeUid) => {
|
|
756
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
757
|
+
return acc;
|
|
758
|
+
},
|
|
759
|
+
{}
|
|
760
|
+
);
|
|
761
|
+
return contentTypeModelsMap;
|
|
762
|
+
},
|
|
763
|
+
async getAllComponents() {
|
|
764
|
+
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
765
|
+
const components = await contentManagerComponentsService.findAllComponents();
|
|
766
|
+
const componentsMap = components.reduce(
|
|
767
|
+
(acc, component) => {
|
|
768
|
+
acc[component.uid] = component;
|
|
769
|
+
return acc;
|
|
770
|
+
},
|
|
771
|
+
{}
|
|
772
|
+
);
|
|
773
|
+
return componentsMap;
|
|
774
|
+
},
|
|
775
|
+
async delete(releaseId) {
|
|
776
|
+
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
358
777
|
populate: {
|
|
359
778
|
actions: {
|
|
360
|
-
|
|
361
|
-
entry: true
|
|
362
|
-
}
|
|
779
|
+
fields: ["id"]
|
|
363
780
|
}
|
|
364
781
|
}
|
|
782
|
+
});
|
|
783
|
+
if (!release2) {
|
|
784
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
365
785
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
369
|
-
}
|
|
370
|
-
if (releaseWithPopulatedActionEntries.releasedAt) {
|
|
371
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
372
|
-
}
|
|
373
|
-
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
374
|
-
throw new utils.errors.ValidationError("No entries to publish");
|
|
375
|
-
}
|
|
376
|
-
const actions = {};
|
|
377
|
-
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
378
|
-
const contentTypeUid = action.contentType;
|
|
379
|
-
if (!actions[contentTypeUid]) {
|
|
380
|
-
actions[contentTypeUid] = {
|
|
381
|
-
publish: [],
|
|
382
|
-
unpublish: []
|
|
383
|
-
};
|
|
786
|
+
if (release2.releasedAt) {
|
|
787
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
384
788
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
789
|
+
await strapi2.db.transaction(async () => {
|
|
790
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
791
|
+
where: {
|
|
792
|
+
id: {
|
|
793
|
+
$in: release2.actions.map((action) => action.id)
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
|
798
|
+
});
|
|
799
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
|
|
800
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
801
|
+
await schedulingService.cancel(release2.id);
|
|
389
802
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
803
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
804
|
+
return release2;
|
|
805
|
+
},
|
|
806
|
+
async publish(releaseId) {
|
|
807
|
+
try {
|
|
808
|
+
const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
|
|
809
|
+
RELEASE_MODEL_UID,
|
|
810
|
+
releaseId,
|
|
811
|
+
{
|
|
812
|
+
populate: {
|
|
813
|
+
actions: {
|
|
814
|
+
populate: {
|
|
815
|
+
entry: {
|
|
816
|
+
fields: ["id"]
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
);
|
|
823
|
+
if (!releaseWithPopulatedActionEntries) {
|
|
824
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
397
825
|
}
|
|
398
|
-
if (
|
|
399
|
-
|
|
826
|
+
if (releaseWithPopulatedActionEntries.releasedAt) {
|
|
827
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
400
828
|
}
|
|
829
|
+
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
830
|
+
throw new utils.errors.ValidationError("No entries to publish");
|
|
831
|
+
}
|
|
832
|
+
const collectionTypeActions = {};
|
|
833
|
+
const singleTypeActions = [];
|
|
834
|
+
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
835
|
+
const contentTypeUid = action.contentType;
|
|
836
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
837
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
838
|
+
collectionTypeActions[contentTypeUid] = {
|
|
839
|
+
entriestoPublishIds: [],
|
|
840
|
+
entriesToUnpublishIds: []
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
if (action.type === "publish") {
|
|
844
|
+
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
845
|
+
} else {
|
|
846
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
847
|
+
}
|
|
848
|
+
} else {
|
|
849
|
+
singleTypeActions.push({
|
|
850
|
+
uid: contentTypeUid,
|
|
851
|
+
action: action.type,
|
|
852
|
+
id: action.entry.id
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
857
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
858
|
+
await strapi2.db.transaction(async () => {
|
|
859
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
860
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
861
|
+
const entry = await strapi2.entityService.findOne(uid, id, { populate });
|
|
862
|
+
try {
|
|
863
|
+
if (action === "publish") {
|
|
864
|
+
await entityManagerService.publish(entry, uid);
|
|
865
|
+
} else {
|
|
866
|
+
await entityManagerService.unpublish(entry, uid);
|
|
867
|
+
}
|
|
868
|
+
} catch (error) {
|
|
869
|
+
if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
|
|
870
|
+
} else {
|
|
871
|
+
throw error;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
876
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
877
|
+
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
878
|
+
const entriesToPublish = await strapi2.entityService.findMany(
|
|
879
|
+
contentTypeUid,
|
|
880
|
+
{
|
|
881
|
+
filters: {
|
|
882
|
+
id: {
|
|
883
|
+
$in: entriestoPublishIds
|
|
884
|
+
}
|
|
885
|
+
},
|
|
886
|
+
populate
|
|
887
|
+
}
|
|
888
|
+
);
|
|
889
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(
|
|
890
|
+
contentTypeUid,
|
|
891
|
+
{
|
|
892
|
+
filters: {
|
|
893
|
+
id: {
|
|
894
|
+
$in: entriesToUnpublishIds
|
|
895
|
+
}
|
|
896
|
+
},
|
|
897
|
+
populate
|
|
898
|
+
}
|
|
899
|
+
);
|
|
900
|
+
if (entriesToPublish.length > 0) {
|
|
901
|
+
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
902
|
+
}
|
|
903
|
+
if (entriesToUnpublish.length > 0) {
|
|
904
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
|
|
909
|
+
data: {
|
|
910
|
+
/*
|
|
911
|
+
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
|
|
912
|
+
*/
|
|
913
|
+
// @ts-expect-error see above
|
|
914
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
915
|
+
},
|
|
916
|
+
populate: {
|
|
917
|
+
actions: {
|
|
918
|
+
// @ts-expect-error is not expecting count but it is working
|
|
919
|
+
count: true
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
924
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
925
|
+
isPublished: true,
|
|
926
|
+
release: release2
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
930
|
+
return release2;
|
|
931
|
+
} catch (error) {
|
|
932
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
933
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
934
|
+
isPublished: false,
|
|
935
|
+
error
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
939
|
+
where: { id: releaseId },
|
|
940
|
+
data: {
|
|
941
|
+
status: "failed"
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
throw error;
|
|
401
945
|
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
946
|
+
},
|
|
947
|
+
async updateAction(actionId, releaseId, update) {
|
|
948
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
949
|
+
where: {
|
|
950
|
+
id: actionId,
|
|
951
|
+
release: {
|
|
952
|
+
id: releaseId,
|
|
953
|
+
releasedAt: {
|
|
954
|
+
$null: true
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
},
|
|
958
|
+
data: update
|
|
959
|
+
});
|
|
960
|
+
if (!updatedAction) {
|
|
961
|
+
throw new utils.errors.NotFoundError(
|
|
962
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
963
|
+
);
|
|
410
964
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
async deleteAction(actionId, releaseId) {
|
|
430
|
-
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
431
|
-
where: {
|
|
432
|
-
id: actionId,
|
|
433
|
-
release: releaseId
|
|
965
|
+
return updatedAction;
|
|
966
|
+
},
|
|
967
|
+
async deleteAction(actionId, releaseId) {
|
|
968
|
+
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
969
|
+
where: {
|
|
970
|
+
id: actionId,
|
|
971
|
+
release: {
|
|
972
|
+
id: releaseId,
|
|
973
|
+
releasedAt: {
|
|
974
|
+
$null: true
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
if (!deletedAction) {
|
|
980
|
+
throw new utils.errors.NotFoundError(
|
|
981
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
982
|
+
);
|
|
434
983
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
984
|
+
this.updateReleaseStatus(releaseId);
|
|
985
|
+
return deletedAction;
|
|
986
|
+
},
|
|
987
|
+
async updateReleaseStatus(releaseId) {
|
|
988
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
989
|
+
this.countActions({
|
|
990
|
+
filters: {
|
|
991
|
+
release: releaseId
|
|
992
|
+
}
|
|
993
|
+
}),
|
|
994
|
+
this.countActions({
|
|
995
|
+
filters: {
|
|
996
|
+
release: releaseId,
|
|
997
|
+
isEntryValid: false
|
|
998
|
+
}
|
|
999
|
+
})
|
|
1000
|
+
]);
|
|
1001
|
+
if (totalActions > 0) {
|
|
1002
|
+
if (invalidActions > 0) {
|
|
1003
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1004
|
+
where: {
|
|
1005
|
+
id: releaseId
|
|
1006
|
+
},
|
|
1007
|
+
data: {
|
|
1008
|
+
status: "blocked"
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1013
|
+
where: {
|
|
1014
|
+
id: releaseId
|
|
1015
|
+
},
|
|
1016
|
+
data: {
|
|
1017
|
+
status: "ready"
|
|
1018
|
+
}
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1022
|
+
where: {
|
|
1023
|
+
id: releaseId
|
|
1024
|
+
},
|
|
1025
|
+
data: {
|
|
1026
|
+
status: "empty"
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
440
1029
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
});
|
|
1030
|
+
};
|
|
1031
|
+
};
|
|
444
1032
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
445
1033
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
446
1034
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
@@ -463,16 +1051,117 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
463
1051
|
if (!contentType) {
|
|
464
1052
|
throw new utils.errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
|
|
465
1053
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
1054
|
+
},
|
|
1055
|
+
async validatePendingReleasesLimit() {
|
|
1056
|
+
const maximumPendingReleases = strapi2.ee.features.get("cms-content-releases")?.options?.maximumReleases || 3;
|
|
1057
|
+
const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
|
|
1058
|
+
filters: {
|
|
1059
|
+
releasedAt: {
|
|
1060
|
+
$null: true
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
if (pendingReleasesCount >= maximumPendingReleases) {
|
|
1065
|
+
throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
|
|
1066
|
+
}
|
|
1067
|
+
},
|
|
1068
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
1069
|
+
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
1070
|
+
filters: {
|
|
1071
|
+
releasedAt: {
|
|
1072
|
+
$null: true
|
|
1073
|
+
},
|
|
1074
|
+
name,
|
|
1075
|
+
...id && { id: { $ne: id } }
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
const isNameUnique = pendingReleases.length === 0;
|
|
1079
|
+
if (!isNameUnique) {
|
|
1080
|
+
throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
|
|
1081
|
+
}
|
|
1082
|
+
},
|
|
1083
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
1084
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
1085
|
+
throw new utils.errors.ValidationError("Scheduled at must be later than now");
|
|
470
1086
|
}
|
|
471
1087
|
}
|
|
472
1088
|
});
|
|
473
|
-
const
|
|
1089
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
1090
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
1091
|
+
return {
|
|
1092
|
+
async set(releaseId, scheduleDate) {
|
|
1093
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
1094
|
+
if (!release2) {
|
|
1095
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1096
|
+
}
|
|
1097
|
+
const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
|
|
1098
|
+
try {
|
|
1099
|
+
await getService("release").publish(releaseId);
|
|
1100
|
+
} catch (error) {
|
|
1101
|
+
}
|
|
1102
|
+
this.cancel(releaseId);
|
|
1103
|
+
});
|
|
1104
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1105
|
+
this.cancel(releaseId);
|
|
1106
|
+
}
|
|
1107
|
+
scheduledJobs.set(releaseId, job);
|
|
1108
|
+
return scheduledJobs;
|
|
1109
|
+
},
|
|
1110
|
+
cancel(releaseId) {
|
|
1111
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1112
|
+
scheduledJobs.get(releaseId).cancel();
|
|
1113
|
+
scheduledJobs.delete(releaseId);
|
|
1114
|
+
}
|
|
1115
|
+
return scheduledJobs;
|
|
1116
|
+
},
|
|
1117
|
+
getAll() {
|
|
1118
|
+
return scheduledJobs;
|
|
1119
|
+
},
|
|
1120
|
+
/**
|
|
1121
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
1122
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
1123
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
1124
|
+
*/
|
|
1125
|
+
async syncFromDatabase() {
|
|
1126
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1127
|
+
where: {
|
|
1128
|
+
scheduledAt: {
|
|
1129
|
+
$gte: /* @__PURE__ */ new Date()
|
|
1130
|
+
},
|
|
1131
|
+
releasedAt: null
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
for (const release2 of releases) {
|
|
1135
|
+
this.set(release2.id, release2.scheduledAt);
|
|
1136
|
+
}
|
|
1137
|
+
return scheduledJobs;
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
};
|
|
1141
|
+
const services = {
|
|
1142
|
+
release: createReleaseService,
|
|
1143
|
+
"release-validation": createReleaseValidationService,
|
|
1144
|
+
...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
|
|
1145
|
+
};
|
|
474
1146
|
const RELEASE_SCHEMA = yup__namespace.object().shape({
|
|
475
|
-
name: yup__namespace.string().trim().required()
|
|
1147
|
+
name: yup__namespace.string().trim().required(),
|
|
1148
|
+
scheduledAt: yup__namespace.string().nullable(),
|
|
1149
|
+
isScheduled: yup__namespace.boolean().optional(),
|
|
1150
|
+
time: yup__namespace.string().when("isScheduled", {
|
|
1151
|
+
is: true,
|
|
1152
|
+
then: yup__namespace.string().trim().required(),
|
|
1153
|
+
otherwise: yup__namespace.string().nullable()
|
|
1154
|
+
}),
|
|
1155
|
+
timezone: yup__namespace.string().when("isScheduled", {
|
|
1156
|
+
is: true,
|
|
1157
|
+
then: yup__namespace.string().required().nullable(),
|
|
1158
|
+
otherwise: yup__namespace.string().nullable()
|
|
1159
|
+
}),
|
|
1160
|
+
date: yup__namespace.string().when("isScheduled", {
|
|
1161
|
+
is: true,
|
|
1162
|
+
then: yup__namespace.string().required().nullable(),
|
|
1163
|
+
otherwise: yup__namespace.string().nullable()
|
|
1164
|
+
})
|
|
476
1165
|
}).required().noUnknown();
|
|
477
1166
|
const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
|
|
478
1167
|
const releaseController = {
|
|
@@ -489,9 +1178,7 @@ const releaseController = {
|
|
|
489
1178
|
const contentTypeUid = query.contentTypeUid;
|
|
490
1179
|
const entryId = query.entryId;
|
|
491
1180
|
const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
|
|
492
|
-
const data = await releaseService.
|
|
493
|
-
hasEntryAttached
|
|
494
|
-
});
|
|
1181
|
+
const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
|
|
495
1182
|
ctx.body = { data };
|
|
496
1183
|
} else {
|
|
497
1184
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
@@ -507,26 +1194,30 @@ const releaseController = {
|
|
|
507
1194
|
}
|
|
508
1195
|
};
|
|
509
1196
|
});
|
|
510
|
-
|
|
1197
|
+
const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
|
|
1198
|
+
where: {
|
|
1199
|
+
releasedAt: null
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
511
1203
|
}
|
|
512
1204
|
},
|
|
513
1205
|
async findOne(ctx) {
|
|
514
1206
|
const id = ctx.params.id;
|
|
515
1207
|
const releaseService = getService("release", { strapi });
|
|
516
1208
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
});
|
|
521
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
1209
|
+
if (!release2) {
|
|
1210
|
+
throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
|
|
1211
|
+
}
|
|
522
1212
|
const count = await releaseService.countActions({
|
|
523
1213
|
filters: {
|
|
524
1214
|
release: id
|
|
525
1215
|
}
|
|
526
1216
|
});
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
1217
|
+
const sanitizedRelease = {
|
|
1218
|
+
...release2,
|
|
1219
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
1220
|
+
};
|
|
530
1221
|
const data = {
|
|
531
1222
|
...sanitizedRelease,
|
|
532
1223
|
actions: {
|
|
@@ -579,8 +1270,27 @@ const releaseController = {
|
|
|
579
1270
|
const id = ctx.params.id;
|
|
580
1271
|
const releaseService = getService("release", { strapi });
|
|
581
1272
|
const release2 = await releaseService.publish(id, { user });
|
|
1273
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1274
|
+
releaseService.countActions({
|
|
1275
|
+
filters: {
|
|
1276
|
+
release: id,
|
|
1277
|
+
type: "publish"
|
|
1278
|
+
}
|
|
1279
|
+
}),
|
|
1280
|
+
releaseService.countActions({
|
|
1281
|
+
filters: {
|
|
1282
|
+
release: id,
|
|
1283
|
+
type: "unpublish"
|
|
1284
|
+
}
|
|
1285
|
+
})
|
|
1286
|
+
]);
|
|
582
1287
|
ctx.body = {
|
|
583
|
-
data: release2
|
|
1288
|
+
data: release2,
|
|
1289
|
+
meta: {
|
|
1290
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1291
|
+
totalPublishedEntries: countPublishActions,
|
|
1292
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1293
|
+
}
|
|
584
1294
|
};
|
|
585
1295
|
}
|
|
586
1296
|
};
|
|
@@ -615,33 +1325,34 @@ const releaseActionController = {
|
|
|
615
1325
|
});
|
|
616
1326
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
617
1327
|
const releaseService = getService("release", { strapi });
|
|
618
|
-
const { results, pagination } = await releaseService.findActions(releaseId,
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
);
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
1328
|
+
const { results, pagination } = await releaseService.findActions(releaseId, {
|
|
1329
|
+
sort: query.groupBy === "action" ? "type" : query.groupBy,
|
|
1330
|
+
...query
|
|
1331
|
+
});
|
|
1332
|
+
const contentTypeOutputSanitizers = results.reduce((acc, action) => {
|
|
1333
|
+
if (acc[action.contentType]) {
|
|
1334
|
+
return acc;
|
|
1335
|
+
}
|
|
1336
|
+
const contentTypePermissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
|
1337
|
+
ability: ctx.state.userAbility,
|
|
1338
|
+
model: action.contentType
|
|
1339
|
+
});
|
|
1340
|
+
acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
|
|
625
1341
|
return acc;
|
|
626
1342
|
}, {});
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
displayName,
|
|
635
|
-
mainFieldValue: action.entry[mainField]
|
|
636
|
-
},
|
|
637
|
-
locale: allLocalesDictionary[action.entry.locale]
|
|
638
|
-
}
|
|
639
|
-
};
|
|
640
|
-
});
|
|
1343
|
+
const sanitizedResults = await utils.async.map(results, async (action) => ({
|
|
1344
|
+
...action,
|
|
1345
|
+
entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
|
|
1346
|
+
}));
|
|
1347
|
+
const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
|
|
1348
|
+
const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
|
|
1349
|
+
const components = await releaseService.getAllComponents();
|
|
641
1350
|
ctx.body = {
|
|
642
|
-
data,
|
|
1351
|
+
data: groupedData,
|
|
643
1352
|
meta: {
|
|
644
|
-
pagination
|
|
1353
|
+
pagination,
|
|
1354
|
+
contentTypes: contentTypes2,
|
|
1355
|
+
components
|
|
645
1356
|
}
|
|
646
1357
|
};
|
|
647
1358
|
},
|
|
@@ -663,10 +1374,8 @@ const releaseActionController = {
|
|
|
663
1374
|
async delete(ctx) {
|
|
664
1375
|
const actionId = ctx.params.actionId;
|
|
665
1376
|
const releaseId = ctx.params.releaseId;
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
releaseId
|
|
669
|
-
);
|
|
1377
|
+
const releaseService = getService("release", { strapi });
|
|
1378
|
+
const deletedReleaseAction = await releaseService.deleteAction(actionId, releaseId);
|
|
670
1379
|
ctx.body = {
|
|
671
1380
|
data: deletedReleaseAction
|
|
672
1381
|
};
|
|
@@ -847,11 +1556,12 @@ const routes = {
|
|
|
847
1556
|
release,
|
|
848
1557
|
"release-action": releaseAction
|
|
849
1558
|
};
|
|
850
|
-
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
851
1559
|
const getPlugin = () => {
|
|
852
|
-
if (features.isEnabled("cms-content-releases")
|
|
1560
|
+
if (strapi.ee.features.isEnabled("cms-content-releases")) {
|
|
853
1561
|
return {
|
|
854
1562
|
register,
|
|
1563
|
+
bootstrap,
|
|
1564
|
+
destroy,
|
|
855
1565
|
contentTypes,
|
|
856
1566
|
services,
|
|
857
1567
|
controllers,
|