@strapi/content-releases 0.0.0-experimental.defd8568ae03ef8d52f86e1f3541979f953c3941 → 0.0.0-experimental.df298029ec6478763dcca7d59fafe8d2ae4ed60a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +17 -1
- package/dist/_chunks/{App-C0DlH0im.js → App-BKB1esYS.js} +434 -405
- package/dist/_chunks/App-BKB1esYS.js.map +1 -0
- package/dist/_chunks/{App-O0ZO-S35.mjs → App-Cne--1Z8.mjs} +431 -400
- package/dist/_chunks/App-Cne--1Z8.mjs.map +1 -0
- package/dist/_chunks/{PurchaseContentReleases-DAHdUpAA.js → PurchaseContentReleases-Be3acS2L.js} +4 -3
- package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js.map +1 -0
- package/dist/_chunks/{PurchaseContentReleases-Ex09YpKR.mjs → PurchaseContentReleases-_MxP6-Dt.mjs} +5 -4
- package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-C1WwGWIH.mjs +178 -0
- package/dist/_chunks/ReleasesSettingsPage-C1WwGWIH.mjs.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-kuXIwpWp.js +178 -0
- package/dist/_chunks/ReleasesSettingsPage-kuXIwpWp.js.map +1 -0
- package/dist/_chunks/{en-DtFJ5ViE.js → en-CmYoEnA7.js} +9 -2
- package/dist/_chunks/en-CmYoEnA7.js.map +1 -0
- package/dist/_chunks/{en-B9Ur3VsE.mjs → en-D0yVZFqf.mjs} +9 -2
- package/dist/_chunks/en-D0yVZFqf.mjs.map +1 -0
- package/dist/_chunks/{index-DoZNNtsb.js → index-5Odi61vw.js} +714 -595
- package/dist/_chunks/index-5Odi61vw.js.map +1 -0
- package/dist/_chunks/{index-DjDPK8kb.mjs → index-Cy7qwpaU.mjs} +723 -602
- package/dist/_chunks/index-Cy7qwpaU.mjs.map +1 -0
- package/dist/_chunks/schemas-BE1LxE9J.js +62 -0
- package/dist/_chunks/schemas-BE1LxE9J.js.map +1 -0
- package/dist/_chunks/schemas-DdA2ic2U.mjs +44 -0
- package/dist/_chunks/schemas-DdA2ic2U.mjs.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +2 -2
- package/dist/admin/src/components/ReleaseAction.d.ts +1 -1
- package/dist/admin/src/components/ReleaseActionMenu.d.ts +3 -3
- package/dist/admin/src/components/{CMReleasesContainer.d.ts → ReleaseActionModal.d.ts} +3 -1
- package/dist/admin/src/components/ReleaseListCell.d.ts +28 -0
- package/dist/admin/src/components/ReleaseModal.d.ts +3 -2
- package/dist/admin/src/components/ReleasesPanel.d.ts +3 -0
- package/dist/admin/src/constants.d.ts +18 -0
- package/dist/admin/src/modules/hooks.d.ts +7 -0
- package/dist/admin/src/pages/ReleasesSettingsPage.d.ts +1 -0
- package/dist/admin/src/services/release.d.ts +53 -370
- package/dist/admin/src/utils/api.d.ts +6 -0
- package/dist/admin/src/utils/time.d.ts +9 -0
- package/dist/admin/src/validation/schemas.d.ts +6 -0
- package/dist/server/index.js +786 -580
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +787 -581
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/src/bootstrap.d.ts.map +1 -1
- package/dist/server/src/constants.d.ts +11 -2
- package/dist/server/src/constants.d.ts.map +1 -1
- package/dist/server/src/content-types/index.d.ts +3 -5
- package/dist/server/src/content-types/index.d.ts.map +1 -1
- package/dist/server/src/content-types/release-action/index.d.ts +3 -5
- package/dist/server/src/content-types/release-action/index.d.ts.map +1 -1
- package/dist/server/src/content-types/release-action/schema.d.ts +3 -5
- package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -1
- package/dist/server/src/controllers/index.d.ts +6 -1
- package/dist/server/src/controllers/index.d.ts.map +1 -1
- package/dist/server/src/controllers/release-action.d.ts.map +1 -1
- package/dist/server/src/controllers/release.d.ts +7 -1
- package/dist/server/src/controllers/release.d.ts.map +1 -1
- package/dist/server/src/controllers/settings.d.ts +11 -0
- package/dist/server/src/controllers/settings.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release-action.d.ts +7 -1
- package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/release.d.ts +2 -0
- package/dist/server/src/controllers/validation/release.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/settings.d.ts +3 -0
- package/dist/server/src/controllers/validation/settings.d.ts.map +1 -0
- package/dist/server/src/index.d.ts +68 -49
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/middlewares/documents.d.ts +6 -0
- package/dist/server/src/middlewares/documents.d.ts.map +1 -0
- package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts +9 -0
- package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts.map +1 -0
- package/dist/server/src/migrations/index.d.ts.map +1 -1
- package/dist/server/src/register.d.ts.map +1 -1
- package/dist/server/src/routes/index.d.ts +16 -0
- package/dist/server/src/routes/index.d.ts.map +1 -1
- package/dist/server/src/routes/release.d.ts.map +1 -1
- package/dist/server/src/routes/settings.d.ts +18 -0
- package/dist/server/src/routes/settings.d.ts.map +1 -0
- package/dist/server/src/services/index.d.ts +40 -38
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/services/release-action.d.ts +38 -0
- package/dist/server/src/services/release-action.d.ts.map +1 -0
- package/dist/server/src/services/release.d.ts +6 -41
- package/dist/server/src/services/release.d.ts.map +1 -1
- package/dist/server/src/services/settings.d.ts +13 -0
- package/dist/server/src/services/settings.d.ts.map +1 -0
- package/dist/server/src/services/validation.d.ts +1 -1
- package/dist/server/src/services/validation.d.ts.map +1 -1
- package/dist/server/src/utils/index.d.ts +29 -8
- package/dist/server/src/utils/index.d.ts.map +1 -1
- package/dist/shared/contracts/release-actions.d.ts +9 -10
- package/dist/shared/contracts/release-actions.d.ts.map +1 -1
- package/dist/shared/contracts/releases.d.ts +9 -7
- package/dist/shared/contracts/releases.d.ts.map +1 -1
- package/dist/shared/contracts/settings.d.ts +39 -0
- package/dist/shared/contracts/settings.d.ts.map +1 -0
- package/package.json +22 -22
- package/dist/_chunks/App-C0DlH0im.js.map +0 -1
- package/dist/_chunks/App-O0ZO-S35.mjs.map +0 -1
- package/dist/_chunks/PurchaseContentReleases-DAHdUpAA.js.map +0 -1
- package/dist/_chunks/PurchaseContentReleases-Ex09YpKR.mjs.map +0 -1
- package/dist/_chunks/en-B9Ur3VsE.mjs.map +0 -1
- package/dist/_chunks/en-DtFJ5ViE.js.map +0 -1
- package/dist/_chunks/index-DjDPK8kb.mjs.map +0 -1
- package/dist/_chunks/index-DoZNNtsb.js.map +0 -1
- package/dist/admin/src/services/axios.d.ts +0 -29
- package/dist/shared/validation-schemas.d.ts +0 -2
- package/dist/shared/validation-schemas.d.ts.map +0 -1
- package/strapi-server.js +0 -3
package/dist/server/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { contentTypes as contentTypes$1, async, setCreatorFields, errors,
|
|
1
|
+
import { contentTypes as contentTypes$1, async, setCreatorFields, errors, yup as yup$1, validateYupSchema } from "@strapi/utils";
|
|
2
2
|
import isEqual from "lodash/isEqual";
|
|
3
3
|
import { difference, keys } from "lodash";
|
|
4
4
|
import _ from "lodash/fp";
|
|
@@ -48,6 +48,23 @@ const ACTIONS = [
|
|
|
48
48
|
displayName: "Add an entry to a release",
|
|
49
49
|
uid: "create-action",
|
|
50
50
|
pluginName: "content-releases"
|
|
51
|
+
},
|
|
52
|
+
// Settings
|
|
53
|
+
{
|
|
54
|
+
uid: "settings.read",
|
|
55
|
+
section: "settings",
|
|
56
|
+
displayName: "Read",
|
|
57
|
+
category: "content releases",
|
|
58
|
+
subCategory: "options",
|
|
59
|
+
pluginName: "content-releases"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
uid: "settings.update",
|
|
63
|
+
section: "settings",
|
|
64
|
+
displayName: "Edit",
|
|
65
|
+
category: "content releases",
|
|
66
|
+
subCategory: "options",
|
|
67
|
+
pluginName: "content-releases"
|
|
51
68
|
}
|
|
52
69
|
];
|
|
53
70
|
const ALLOWED_WEBHOOK_EVENTS = {
|
|
@@ -56,16 +73,13 @@ const ALLOWED_WEBHOOK_EVENTS = {
|
|
|
56
73
|
const getService = (name, { strapi: strapi2 }) => {
|
|
57
74
|
return strapi2.plugin("content-releases").service(name);
|
|
58
75
|
};
|
|
59
|
-
const
|
|
76
|
+
const getDraftEntryValidStatus = async ({ contentType, documentId, locale }, { strapi: strapi2 }) => {
|
|
60
77
|
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
61
|
-
const populate = await populateBuilderService(
|
|
62
|
-
const entry = await strapi2
|
|
63
|
-
|
|
64
|
-
populate
|
|
65
|
-
});
|
|
66
|
-
return entry;
|
|
78
|
+
const populate = await populateBuilderService(contentType).populateDeep(Infinity).build();
|
|
79
|
+
const entry = await getEntry({ contentType, documentId, locale, populate }, { strapi: strapi2 });
|
|
80
|
+
return isEntryValid(contentType, entry, { strapi: strapi2 });
|
|
67
81
|
};
|
|
68
|
-
const
|
|
82
|
+
const isEntryValid = async (contentTypeUid, entry, { strapi: strapi2 }) => {
|
|
69
83
|
try {
|
|
70
84
|
await strapi2.entityValidator.validateEntityCreation(
|
|
71
85
|
strapi2.getModel(contentTypeUid),
|
|
@@ -79,6 +93,38 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 }) =
|
|
|
79
93
|
return false;
|
|
80
94
|
}
|
|
81
95
|
};
|
|
96
|
+
const getEntry = async ({
|
|
97
|
+
contentType,
|
|
98
|
+
documentId,
|
|
99
|
+
locale,
|
|
100
|
+
populate,
|
|
101
|
+
status = "draft"
|
|
102
|
+
}, { strapi: strapi2 }) => {
|
|
103
|
+
if (documentId) {
|
|
104
|
+
return strapi2.documents(contentType).findOne({ documentId, locale, populate, status });
|
|
105
|
+
}
|
|
106
|
+
return strapi2.documents(contentType).findFirst({ locale, populate, status });
|
|
107
|
+
};
|
|
108
|
+
const getEntryStatus = async (contentType, entry) => {
|
|
109
|
+
if (entry.publishedAt) {
|
|
110
|
+
return "published";
|
|
111
|
+
}
|
|
112
|
+
const publishedEntry = await strapi.documents(contentType).findOne({
|
|
113
|
+
documentId: entry.documentId,
|
|
114
|
+
locale: entry.locale,
|
|
115
|
+
status: "published",
|
|
116
|
+
fields: ["updatedAt"]
|
|
117
|
+
});
|
|
118
|
+
if (!publishedEntry) {
|
|
119
|
+
return "draft";
|
|
120
|
+
}
|
|
121
|
+
const entryUpdatedAt = new Date(entry.updatedAt).getTime();
|
|
122
|
+
const publishedEntryUpdatedAt = new Date(publishedEntry.updatedAt).getTime();
|
|
123
|
+
if (entryUpdatedAt > publishedEntryUpdatedAt) {
|
|
124
|
+
return "modified";
|
|
125
|
+
}
|
|
126
|
+
return "published";
|
|
127
|
+
};
|
|
82
128
|
async function deleteActionsOnDisableDraftAndPublish({
|
|
83
129
|
oldContentTypes,
|
|
84
130
|
contentTypes: contentTypes2
|
|
@@ -124,20 +170,22 @@ async function migrateIsValidAndStatusReleases() {
|
|
|
124
170
|
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
125
171
|
for (const action of notValidatedActions) {
|
|
126
172
|
if (action.entry) {
|
|
127
|
-
const
|
|
128
|
-
|
|
173
|
+
const isEntryValid2 = getDraftEntryValidStatus(
|
|
174
|
+
{
|
|
175
|
+
contentType: action.contentType,
|
|
176
|
+
documentId: action.entryDocumentId,
|
|
177
|
+
locale: action.locale
|
|
178
|
+
},
|
|
179
|
+
{ strapi }
|
|
180
|
+
);
|
|
181
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
182
|
+
where: {
|
|
183
|
+
id: action.id
|
|
184
|
+
},
|
|
185
|
+
data: {
|
|
186
|
+
isEntryValid: isEntryValid2
|
|
187
|
+
}
|
|
129
188
|
});
|
|
130
|
-
if (populatedEntry) {
|
|
131
|
-
const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
|
|
132
|
-
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
133
|
-
where: {
|
|
134
|
-
id: action.id
|
|
135
|
-
},
|
|
136
|
-
data: {
|
|
137
|
-
isEntryValid
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
189
|
}
|
|
142
190
|
}
|
|
143
191
|
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
@@ -181,24 +229,24 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
|
|
|
181
229
|
}
|
|
182
230
|
});
|
|
183
231
|
await async.map(actions, async (action) => {
|
|
184
|
-
if (action.entry && action.release) {
|
|
185
|
-
const
|
|
186
|
-
|
|
232
|
+
if (action.entry && action.release && action.type === "publish") {
|
|
233
|
+
const isEntryValid2 = await getDraftEntryValidStatus(
|
|
234
|
+
{
|
|
235
|
+
contentType: contentTypeUID,
|
|
236
|
+
documentId: action.entryDocumentId,
|
|
237
|
+
locale: action.locale
|
|
238
|
+
},
|
|
239
|
+
{ strapi }
|
|
240
|
+
);
|
|
241
|
+
releasesAffected.add(action.release.id);
|
|
242
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
243
|
+
where: {
|
|
244
|
+
id: action.id
|
|
245
|
+
},
|
|
246
|
+
data: {
|
|
247
|
+
isEntryValid: isEntryValid2
|
|
248
|
+
}
|
|
187
249
|
});
|
|
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
250
|
}
|
|
203
251
|
});
|
|
204
252
|
}
|
|
@@ -255,9 +303,42 @@ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: conte
|
|
|
255
303
|
}
|
|
256
304
|
}
|
|
257
305
|
}
|
|
306
|
+
const addEntryDocumentToReleaseActions = {
|
|
307
|
+
name: "content-releases::5.0.0-add-entry-document-id-to-release-actions",
|
|
308
|
+
async up(trx, db) {
|
|
309
|
+
const hasTable = await trx.schema.hasTable("strapi_release_actions");
|
|
310
|
+
if (!hasTable) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const hasPolymorphicColumn = await trx.schema.hasColumn("strapi_release_actions", "target_id");
|
|
314
|
+
if (hasPolymorphicColumn) {
|
|
315
|
+
const hasEntryDocumentIdColumn = await trx.schema.hasColumn(
|
|
316
|
+
"strapi_release_actions",
|
|
317
|
+
"entry_document_id"
|
|
318
|
+
);
|
|
319
|
+
if (!hasEntryDocumentIdColumn) {
|
|
320
|
+
await trx.schema.alterTable("strapi_release_actions", (table) => {
|
|
321
|
+
table.string("entry_document_id");
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
const releaseActions = await trx.select("*").from("strapi_release_actions");
|
|
325
|
+
async.map(releaseActions, async (action) => {
|
|
326
|
+
const { target_type, target_id } = action;
|
|
327
|
+
const entry = await db.query(target_type).findOne({ where: { id: target_id } });
|
|
328
|
+
if (entry) {
|
|
329
|
+
await trx("strapi_release_actions").update({ entry_document_id: entry.documentId }).where("id", action.id);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
async down() {
|
|
335
|
+
throw new Error("not implemented");
|
|
336
|
+
}
|
|
337
|
+
};
|
|
258
338
|
const register = async ({ strapi: strapi2 }) => {
|
|
259
339
|
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
260
340
|
await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
|
|
341
|
+
strapi2.db.migrations.providers.internal.register(addEntryDocumentToReleaseActions);
|
|
261
342
|
strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
|
|
262
343
|
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
263
344
|
}
|
|
@@ -267,6 +348,104 @@ const register = async ({ strapi: strapi2 }) => {
|
|
|
267
348
|
graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
|
|
268
349
|
}
|
|
269
350
|
};
|
|
351
|
+
const updateActionsStatusAndUpdateReleaseStatus = async (contentType, entry) => {
|
|
352
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
353
|
+
where: {
|
|
354
|
+
actions: {
|
|
355
|
+
contentType,
|
|
356
|
+
entryDocumentId: entry.documentId,
|
|
357
|
+
locale: entry.locale
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
const entryStatus = await isEntryValid(contentType, entry, { strapi });
|
|
362
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
363
|
+
where: {
|
|
364
|
+
contentType,
|
|
365
|
+
entryDocumentId: entry.documentId,
|
|
366
|
+
locale: entry.locale
|
|
367
|
+
},
|
|
368
|
+
data: {
|
|
369
|
+
isEntryValid: entryStatus
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
for (const release2 of releases) {
|
|
373
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const deleteActionsAndUpdateReleaseStatus = async (params) => {
|
|
377
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
378
|
+
where: {
|
|
379
|
+
actions: params
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
383
|
+
where: params
|
|
384
|
+
});
|
|
385
|
+
for (const release2 of releases) {
|
|
386
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
const deleteActionsOnDelete = async (ctx, next) => {
|
|
390
|
+
if (ctx.action !== "delete") {
|
|
391
|
+
return next();
|
|
392
|
+
}
|
|
393
|
+
if (!contentTypes$1.hasDraftAndPublish(ctx.contentType)) {
|
|
394
|
+
return next();
|
|
395
|
+
}
|
|
396
|
+
const contentType = ctx.contentType.uid;
|
|
397
|
+
const { documentId, locale } = ctx.params;
|
|
398
|
+
const result = await next();
|
|
399
|
+
if (!result) {
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
deleteActionsAndUpdateReleaseStatus({
|
|
404
|
+
contentType,
|
|
405
|
+
entryDocumentId: documentId,
|
|
406
|
+
...locale !== "*" && { locale }
|
|
407
|
+
});
|
|
408
|
+
} catch (error) {
|
|
409
|
+
strapi.log.error("Error while deleting release actions after delete", {
|
|
410
|
+
error
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
return result;
|
|
414
|
+
};
|
|
415
|
+
const updateActionsOnUpdate = async (ctx, next) => {
|
|
416
|
+
if (ctx.action !== "update") {
|
|
417
|
+
return next();
|
|
418
|
+
}
|
|
419
|
+
if (!contentTypes$1.hasDraftAndPublish(ctx.contentType)) {
|
|
420
|
+
return next();
|
|
421
|
+
}
|
|
422
|
+
const contentType = ctx.contentType.uid;
|
|
423
|
+
const result = await next();
|
|
424
|
+
if (!result) {
|
|
425
|
+
return result;
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
updateActionsStatusAndUpdateReleaseStatus(contentType, result);
|
|
429
|
+
} catch (error) {
|
|
430
|
+
strapi.log.error("Error while updating release actions after update", {
|
|
431
|
+
error
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
435
|
+
};
|
|
436
|
+
const deleteReleasesActionsAndUpdateReleaseStatus = async (params) => {
|
|
437
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
438
|
+
where: {
|
|
439
|
+
actions: params
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
443
|
+
where: params
|
|
444
|
+
});
|
|
445
|
+
for (const release2 of releases) {
|
|
446
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
270
449
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
271
450
|
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
272
451
|
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
@@ -274,115 +453,29 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
274
453
|
);
|
|
275
454
|
strapi2.db.lifecycles.subscribe({
|
|
276
455
|
models: contentTypesWithDraftAndPublish,
|
|
277
|
-
async afterDelete(event) {
|
|
278
|
-
try {
|
|
279
|
-
const { model, result } = event;
|
|
280
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
281
|
-
const { id } = result;
|
|
282
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
283
|
-
where: {
|
|
284
|
-
actions: {
|
|
285
|
-
target_type: model.uid,
|
|
286
|
-
target_id: id
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
291
|
-
where: {
|
|
292
|
-
target_type: model.uid,
|
|
293
|
-
target_id: id
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
for (const release2 of releases) {
|
|
297
|
-
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
} catch (error) {
|
|
301
|
-
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
302
|
-
}
|
|
303
|
-
},
|
|
304
|
-
/**
|
|
305
|
-
* deleteMany hook doesn't return the deleted entries ids
|
|
306
|
-
* so we need to fetch them before deleting the entries to save the ids on our state
|
|
307
|
-
*/
|
|
308
|
-
async beforeDeleteMany(event) {
|
|
309
|
-
const { model, params } = event;
|
|
310
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
311
|
-
const { where } = params;
|
|
312
|
-
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
313
|
-
event.state.entriesToDelete = entriesToDelete;
|
|
314
|
-
}
|
|
315
|
-
},
|
|
316
456
|
/**
|
|
317
|
-
*
|
|
318
|
-
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
457
|
+
* deleteMany is still used outside documents service, for example when deleting a locale
|
|
319
458
|
*/
|
|
320
459
|
async afterDeleteMany(event) {
|
|
321
460
|
try {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
target_id: {
|
|
330
|
-
$in: entriesToDelete.map((entry) => entry.id)
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
336
|
-
where: {
|
|
337
|
-
target_type: model.uid,
|
|
338
|
-
target_id: {
|
|
339
|
-
$in: entriesToDelete.map((entry) => entry.id)
|
|
340
|
-
}
|
|
341
|
-
}
|
|
461
|
+
const model = strapi2.getModel(event.model.uid);
|
|
462
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
463
|
+
const { where } = event.params;
|
|
464
|
+
deleteReleasesActionsAndUpdateReleaseStatus({
|
|
465
|
+
contentType: model.uid,
|
|
466
|
+
locale: where.locale ?? null,
|
|
467
|
+
...where.documentId && { entryDocumentId: where.documentId }
|
|
342
468
|
});
|
|
343
|
-
for (const release2 of releases) {
|
|
344
|
-
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
345
|
-
}
|
|
346
469
|
}
|
|
347
470
|
} catch (error) {
|
|
348
471
|
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
349
472
|
error
|
|
350
473
|
});
|
|
351
474
|
}
|
|
352
|
-
},
|
|
353
|
-
async afterUpdate(event) {
|
|
354
|
-
try {
|
|
355
|
-
const { model, result } = event;
|
|
356
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
357
|
-
const isEntryValid = await getEntryValidStatus(model.uid, result, {
|
|
358
|
-
strapi: strapi2
|
|
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
|
-
}
|
|
384
475
|
}
|
|
385
476
|
});
|
|
477
|
+
strapi2.documents.use(deleteActionsOnDelete);
|
|
478
|
+
strapi2.documents.use(updateActionsOnUpdate);
|
|
386
479
|
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
387
480
|
strapi2.log.error(
|
|
388
481
|
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
@@ -474,15 +567,13 @@ const schema = {
|
|
|
474
567
|
enum: ["publish", "unpublish"],
|
|
475
568
|
required: true
|
|
476
569
|
},
|
|
477
|
-
entry: {
|
|
478
|
-
type: "relation",
|
|
479
|
-
relation: "morphToOne",
|
|
480
|
-
configurable: false
|
|
481
|
-
},
|
|
482
570
|
contentType: {
|
|
483
571
|
type: "string",
|
|
484
572
|
required: true
|
|
485
573
|
},
|
|
574
|
+
entryDocumentId: {
|
|
575
|
+
type: "string"
|
|
576
|
+
},
|
|
486
577
|
locale: {
|
|
487
578
|
type: "string"
|
|
488
579
|
},
|
|
@@ -504,18 +595,6 @@ const contentTypes = {
|
|
|
504
595
|
release: release$1,
|
|
505
596
|
"release-action": releaseAction$1
|
|
506
597
|
};
|
|
507
|
-
const getGroupName = (queryValue) => {
|
|
508
|
-
switch (queryValue) {
|
|
509
|
-
case "contentType":
|
|
510
|
-
return "contentType.displayName";
|
|
511
|
-
case "action":
|
|
512
|
-
return "type";
|
|
513
|
-
case "locale":
|
|
514
|
-
return _.getOr("No locale", "locale.name");
|
|
515
|
-
default:
|
|
516
|
-
return "contentType.displayName";
|
|
517
|
-
}
|
|
518
|
-
};
|
|
519
598
|
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
520
599
|
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
521
600
|
strapi2.eventHub.emit(event, {
|
|
@@ -524,93 +603,32 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
524
603
|
release: release2
|
|
525
604
|
});
|
|
526
605
|
};
|
|
527
|
-
const publishSingleTypeAction = async (uid, actionType, entryId) => {
|
|
528
|
-
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
529
|
-
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
530
|
-
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
531
|
-
const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
|
|
532
|
-
try {
|
|
533
|
-
if (actionType === "publish") {
|
|
534
|
-
await entityManagerService.publish(entry, uid);
|
|
535
|
-
} else {
|
|
536
|
-
await entityManagerService.unpublish(entry, uid);
|
|
537
|
-
}
|
|
538
|
-
} catch (error) {
|
|
539
|
-
if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
540
|
-
;
|
|
541
|
-
else {
|
|
542
|
-
throw error;
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
};
|
|
546
|
-
const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
|
|
547
|
-
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
548
|
-
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
549
|
-
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
550
|
-
const entriesToPublish = await strapi2.entityService.findMany(uid, {
|
|
551
|
-
filters: {
|
|
552
|
-
id: {
|
|
553
|
-
$in: entriesToPublishIds
|
|
554
|
-
}
|
|
555
|
-
},
|
|
556
|
-
populate
|
|
557
|
-
});
|
|
558
|
-
const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
|
|
559
|
-
filters: {
|
|
560
|
-
id: {
|
|
561
|
-
$in: entriestoUnpublishIds
|
|
562
|
-
}
|
|
563
|
-
},
|
|
564
|
-
populate
|
|
565
|
-
});
|
|
566
|
-
if (entriesToPublish.length > 0) {
|
|
567
|
-
await entityManagerService.publishMany(entriesToPublish, uid);
|
|
568
|
-
}
|
|
569
|
-
if (entriesToUnpublish.length > 0) {
|
|
570
|
-
await entityManagerService.unpublishMany(entriesToUnpublish, uid);
|
|
571
|
-
}
|
|
572
|
-
};
|
|
573
606
|
const getFormattedActions = async (releaseId) => {
|
|
574
607
|
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
575
608
|
where: {
|
|
576
609
|
release: {
|
|
577
610
|
id: releaseId
|
|
578
611
|
}
|
|
579
|
-
},
|
|
580
|
-
populate: {
|
|
581
|
-
entry: {
|
|
582
|
-
fields: ["id"]
|
|
583
|
-
}
|
|
584
612
|
}
|
|
585
613
|
});
|
|
586
614
|
if (actions.length === 0) {
|
|
587
615
|
throw new errors.ValidationError("No entries to publish");
|
|
588
616
|
}
|
|
589
|
-
const
|
|
590
|
-
const singleTypeActions = [];
|
|
617
|
+
const formattedActions = {};
|
|
591
618
|
for (const action of actions) {
|
|
592
619
|
const contentTypeUid = action.contentType;
|
|
593
|
-
if (
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
if (action.type === "publish") {
|
|
601
|
-
collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
|
|
602
|
-
} else {
|
|
603
|
-
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
604
|
-
}
|
|
605
|
-
} else {
|
|
606
|
-
singleTypeActions.push({
|
|
607
|
-
uid: contentTypeUid,
|
|
608
|
-
action: action.type,
|
|
609
|
-
id: action.entry.id
|
|
610
|
-
});
|
|
620
|
+
if (!formattedActions[contentTypeUid]) {
|
|
621
|
+
formattedActions[contentTypeUid] = {
|
|
622
|
+
publish: [],
|
|
623
|
+
unpublish: []
|
|
624
|
+
};
|
|
611
625
|
}
|
|
626
|
+
formattedActions[contentTypeUid][action.type].push({
|
|
627
|
+
documentId: action.entryDocumentId,
|
|
628
|
+
locale: action.locale
|
|
629
|
+
});
|
|
612
630
|
}
|
|
613
|
-
return
|
|
631
|
+
return formattedActions;
|
|
614
632
|
};
|
|
615
633
|
return {
|
|
616
634
|
async create(releaseData, { user }) {
|
|
@@ -657,91 +675,10 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
657
675
|
}
|
|
658
676
|
});
|
|
659
677
|
},
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
666
|
-
where: {
|
|
667
|
-
actions: {
|
|
668
|
-
target_type: contentTypeUid,
|
|
669
|
-
target_id: {
|
|
670
|
-
$in: entries
|
|
671
|
-
}
|
|
672
|
-
},
|
|
673
|
-
releasedAt: {
|
|
674
|
-
$null: true
|
|
675
|
-
}
|
|
676
|
-
},
|
|
677
|
-
populate: {
|
|
678
|
-
// Filter the action to get only the content type entry
|
|
679
|
-
actions: {
|
|
680
|
-
where: {
|
|
681
|
-
target_type: contentTypeUid,
|
|
682
|
-
target_id: {
|
|
683
|
-
$in: entries
|
|
684
|
-
}
|
|
685
|
-
},
|
|
686
|
-
populate: {
|
|
687
|
-
entry: {
|
|
688
|
-
select: ["id"]
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
});
|
|
694
|
-
return releases.map((release2) => {
|
|
695
|
-
if (release2.actions?.length) {
|
|
696
|
-
const actionsForEntry = release2.actions;
|
|
697
|
-
delete release2.actions;
|
|
698
|
-
return {
|
|
699
|
-
...release2,
|
|
700
|
-
actions: actionsForEntry
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
return release2;
|
|
704
|
-
});
|
|
705
|
-
},
|
|
706
|
-
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
707
|
-
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
708
|
-
where: {
|
|
709
|
-
releasedAt: {
|
|
710
|
-
$null: true
|
|
711
|
-
},
|
|
712
|
-
actions: {
|
|
713
|
-
target_type: contentTypeUid,
|
|
714
|
-
target_id: entryId
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
});
|
|
718
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
719
|
-
where: {
|
|
720
|
-
$or: [
|
|
721
|
-
{
|
|
722
|
-
id: {
|
|
723
|
-
$notIn: releasesRelated.map((release2) => release2.id)
|
|
724
|
-
}
|
|
725
|
-
},
|
|
726
|
-
{
|
|
727
|
-
actions: null
|
|
728
|
-
}
|
|
729
|
-
],
|
|
730
|
-
releasedAt: {
|
|
731
|
-
$null: true
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
});
|
|
735
|
-
return releases.map((release2) => {
|
|
736
|
-
if (release2.actions?.length) {
|
|
737
|
-
const [actionForEntry] = release2.actions;
|
|
738
|
-
delete release2.actions;
|
|
739
|
-
return {
|
|
740
|
-
...release2,
|
|
741
|
-
action: actionForEntry
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
return release2;
|
|
678
|
+
findMany(query) {
|
|
679
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
|
|
680
|
+
return strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
681
|
+
...dbQuery
|
|
745
682
|
});
|
|
746
683
|
},
|
|
747
684
|
async update(id, releaseData, { user }) {
|
|
@@ -777,131 +714,6 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
777
714
|
strapi2.telemetry.send("didUpdateContentRelease");
|
|
778
715
|
return updatedRelease;
|
|
779
716
|
},
|
|
780
|
-
async createAction(releaseId, action) {
|
|
781
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
782
|
-
strapi: strapi2
|
|
783
|
-
});
|
|
784
|
-
await Promise.all([
|
|
785
|
-
validateEntryContentType(action.entry.contentType),
|
|
786
|
-
validateUniqueEntry(releaseId, action)
|
|
787
|
-
]);
|
|
788
|
-
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
|
|
789
|
-
if (!release2) {
|
|
790
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
791
|
-
}
|
|
792
|
-
if (release2.releasedAt) {
|
|
793
|
-
throw new errors.ValidationError("Release already published");
|
|
794
|
-
}
|
|
795
|
-
const { entry, type } = action;
|
|
796
|
-
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
797
|
-
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
798
|
-
const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
|
|
799
|
-
data: {
|
|
800
|
-
type,
|
|
801
|
-
contentType: entry.contentType,
|
|
802
|
-
locale: entry.locale,
|
|
803
|
-
isEntryValid,
|
|
804
|
-
entry: {
|
|
805
|
-
id: entry.id,
|
|
806
|
-
__type: entry.contentType,
|
|
807
|
-
__pivot: { field: "entry" }
|
|
808
|
-
},
|
|
809
|
-
release: releaseId
|
|
810
|
-
},
|
|
811
|
-
populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
|
|
812
|
-
});
|
|
813
|
-
this.updateReleaseStatus(releaseId);
|
|
814
|
-
return releaseAction2;
|
|
815
|
-
},
|
|
816
|
-
async findActions(releaseId, query) {
|
|
817
|
-
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
818
|
-
where: { id: releaseId },
|
|
819
|
-
select: ["id"]
|
|
820
|
-
});
|
|
821
|
-
if (!release2) {
|
|
822
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
823
|
-
}
|
|
824
|
-
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
825
|
-
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
826
|
-
...dbQuery,
|
|
827
|
-
populate: {
|
|
828
|
-
entry: {
|
|
829
|
-
populate: "*"
|
|
830
|
-
}
|
|
831
|
-
},
|
|
832
|
-
where: {
|
|
833
|
-
release: releaseId
|
|
834
|
-
}
|
|
835
|
-
});
|
|
836
|
-
},
|
|
837
|
-
async countActions(query) {
|
|
838
|
-
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
839
|
-
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
|
|
840
|
-
},
|
|
841
|
-
async groupActions(actions, groupBy) {
|
|
842
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
843
|
-
if (!acc.includes(action.contentType)) {
|
|
844
|
-
acc.push(action.contentType);
|
|
845
|
-
}
|
|
846
|
-
return acc;
|
|
847
|
-
}, []);
|
|
848
|
-
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(contentTypeUids);
|
|
849
|
-
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
850
|
-
const formattedData = actions.map((action) => {
|
|
851
|
-
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
852
|
-
return {
|
|
853
|
-
...action,
|
|
854
|
-
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
855
|
-
contentType: {
|
|
856
|
-
displayName,
|
|
857
|
-
mainFieldValue: action.entry[mainField],
|
|
858
|
-
uid: action.contentType
|
|
859
|
-
}
|
|
860
|
-
};
|
|
861
|
-
});
|
|
862
|
-
const groupName = getGroupName(groupBy);
|
|
863
|
-
return _.groupBy(groupName)(formattedData);
|
|
864
|
-
},
|
|
865
|
-
async getLocalesDataForActions() {
|
|
866
|
-
if (!strapi2.plugin("i18n")) {
|
|
867
|
-
return {};
|
|
868
|
-
}
|
|
869
|
-
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
870
|
-
return allLocales.reduce((acc, locale) => {
|
|
871
|
-
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
872
|
-
return acc;
|
|
873
|
-
}, {});
|
|
874
|
-
},
|
|
875
|
-
async getContentTypesDataForActions(contentTypesUids) {
|
|
876
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
877
|
-
const contentTypesData = {};
|
|
878
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
879
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
880
|
-
uid: contentTypeUid
|
|
881
|
-
});
|
|
882
|
-
contentTypesData[contentTypeUid] = {
|
|
883
|
-
mainField: contentTypeConfig.settings.mainField,
|
|
884
|
-
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
|
-
return contentTypesData;
|
|
888
|
-
},
|
|
889
|
-
getContentTypeModelsFromActions(actions) {
|
|
890
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
891
|
-
if (!acc.includes(action.contentType)) {
|
|
892
|
-
acc.push(action.contentType);
|
|
893
|
-
}
|
|
894
|
-
return acc;
|
|
895
|
-
}, []);
|
|
896
|
-
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
897
|
-
(acc, contentTypeUid) => {
|
|
898
|
-
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
899
|
-
return acc;
|
|
900
|
-
},
|
|
901
|
-
{}
|
|
902
|
-
);
|
|
903
|
-
return contentTypeModelsMap;
|
|
904
|
-
},
|
|
905
717
|
async getAllComponents() {
|
|
906
718
|
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
907
719
|
const components = await contentManagerComponentsService.findAllComponents();
|
|
@@ -967,20 +779,19 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
967
779
|
}
|
|
968
780
|
try {
|
|
969
781
|
strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
|
|
970
|
-
const
|
|
971
|
-
await strapi2.db.transaction(
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
});
|
|
782
|
+
const formattedActions = await getFormattedActions(releaseId);
|
|
783
|
+
await strapi2.db.transaction(
|
|
784
|
+
async () => Promise.all(
|
|
785
|
+
Object.keys(formattedActions).map(async (contentTypeUid) => {
|
|
786
|
+
const contentType = contentTypeUid;
|
|
787
|
+
const { publish, unpublish } = formattedActions[contentType];
|
|
788
|
+
return Promise.all([
|
|
789
|
+
...publish.map((params) => strapi2.documents(contentType).publish(params)),
|
|
790
|
+
...unpublish.map((params) => strapi2.documents(contentType).unpublish(params))
|
|
791
|
+
]);
|
|
792
|
+
})
|
|
793
|
+
)
|
|
794
|
+
);
|
|
984
795
|
const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
985
796
|
where: {
|
|
986
797
|
id: releaseId
|
|
@@ -1010,13 +821,226 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
1010
821
|
};
|
|
1011
822
|
}
|
|
1012
823
|
});
|
|
1013
|
-
if (error instanceof Error) {
|
|
1014
|
-
throw error;
|
|
824
|
+
if (error instanceof Error) {
|
|
825
|
+
throw error;
|
|
826
|
+
}
|
|
827
|
+
return release2;
|
|
828
|
+
},
|
|
829
|
+
async updateReleaseStatus(releaseId) {
|
|
830
|
+
const releaseActionService = getService("release-action", { strapi: strapi2 });
|
|
831
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
832
|
+
releaseActionService.countActions({
|
|
833
|
+
filters: {
|
|
834
|
+
release: releaseId
|
|
835
|
+
}
|
|
836
|
+
}),
|
|
837
|
+
releaseActionService.countActions({
|
|
838
|
+
filters: {
|
|
839
|
+
release: releaseId,
|
|
840
|
+
isEntryValid: false
|
|
841
|
+
}
|
|
842
|
+
})
|
|
843
|
+
]);
|
|
844
|
+
if (totalActions > 0) {
|
|
845
|
+
if (invalidActions > 0) {
|
|
846
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
847
|
+
where: {
|
|
848
|
+
id: releaseId
|
|
849
|
+
},
|
|
850
|
+
data: {
|
|
851
|
+
status: "blocked"
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
856
|
+
where: {
|
|
857
|
+
id: releaseId
|
|
858
|
+
},
|
|
859
|
+
data: {
|
|
860
|
+
status: "ready"
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
865
|
+
where: {
|
|
866
|
+
id: releaseId
|
|
867
|
+
},
|
|
868
|
+
data: {
|
|
869
|
+
status: "empty"
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
};
|
|
875
|
+
const getGroupName = (queryValue) => {
|
|
876
|
+
switch (queryValue) {
|
|
877
|
+
case "contentType":
|
|
878
|
+
return "contentType.displayName";
|
|
879
|
+
case "type":
|
|
880
|
+
return "type";
|
|
881
|
+
case "locale":
|
|
882
|
+
return _.getOr("No locale", "locale.name");
|
|
883
|
+
default:
|
|
884
|
+
return "contentType.displayName";
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
const createReleaseActionService = ({ strapi: strapi2 }) => {
|
|
888
|
+
const getLocalesDataForActions = async () => {
|
|
889
|
+
if (!strapi2.plugin("i18n")) {
|
|
890
|
+
return {};
|
|
891
|
+
}
|
|
892
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
893
|
+
return allLocales.reduce((acc, locale) => {
|
|
894
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
895
|
+
return acc;
|
|
896
|
+
}, {});
|
|
897
|
+
};
|
|
898
|
+
const getContentTypesDataForActions = async (contentTypesUids) => {
|
|
899
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
900
|
+
const contentTypesData = {};
|
|
901
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
902
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
903
|
+
uid: contentTypeUid
|
|
904
|
+
});
|
|
905
|
+
contentTypesData[contentTypeUid] = {
|
|
906
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
907
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
return contentTypesData;
|
|
911
|
+
};
|
|
912
|
+
return {
|
|
913
|
+
async create(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
|
|
914
|
+
const { validateEntryData, validateUniqueEntry } = getService("release-validation", {
|
|
915
|
+
strapi: strapi2
|
|
916
|
+
});
|
|
917
|
+
await Promise.all([
|
|
918
|
+
validateEntryData(action.contentType, action.entryDocumentId),
|
|
919
|
+
validateUniqueEntry(releaseId, action)
|
|
920
|
+
]);
|
|
921
|
+
const model = strapi2.contentType(action.contentType);
|
|
922
|
+
if (model.kind === "singleType") {
|
|
923
|
+
const document = await strapi2.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
924
|
+
if (!document) {
|
|
925
|
+
throw new errors.NotFoundError(`No entry found for contentType ${action.contentType}`);
|
|
926
|
+
}
|
|
927
|
+
action.entryDocumentId = document.documentId;
|
|
928
|
+
}
|
|
929
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
|
|
930
|
+
if (!release2) {
|
|
931
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
932
|
+
}
|
|
933
|
+
if (release2.releasedAt) {
|
|
934
|
+
throw new errors.ValidationError("Release already published");
|
|
935
|
+
}
|
|
936
|
+
const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
|
|
937
|
+
{
|
|
938
|
+
contentType: action.contentType,
|
|
939
|
+
documentId: action.entryDocumentId,
|
|
940
|
+
locale: action.locale
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
strapi: strapi2
|
|
944
|
+
}
|
|
945
|
+
) : true;
|
|
946
|
+
const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
|
|
947
|
+
data: {
|
|
948
|
+
...action,
|
|
949
|
+
release: release2.id,
|
|
950
|
+
isEntryValid: actionStatus
|
|
951
|
+
},
|
|
952
|
+
populate: { release: { select: ["id"] } }
|
|
953
|
+
});
|
|
954
|
+
if (!disableUpdateReleaseStatus) {
|
|
955
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
1015
956
|
}
|
|
1016
|
-
return
|
|
957
|
+
return releaseAction2;
|
|
1017
958
|
},
|
|
1018
|
-
async
|
|
1019
|
-
const
|
|
959
|
+
async findPage(releaseId, query) {
|
|
960
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
961
|
+
where: { id: releaseId },
|
|
962
|
+
select: ["id"]
|
|
963
|
+
});
|
|
964
|
+
if (!release2) {
|
|
965
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
966
|
+
}
|
|
967
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
968
|
+
const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
969
|
+
...dbQuery,
|
|
970
|
+
where: {
|
|
971
|
+
release: releaseId
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
975
|
+
const actionsWithEntry = await async.map(actions, async (action) => {
|
|
976
|
+
const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
|
|
977
|
+
const entry = await getEntry(
|
|
978
|
+
{
|
|
979
|
+
contentType: action.contentType,
|
|
980
|
+
documentId: action.entryDocumentId,
|
|
981
|
+
locale: action.locale,
|
|
982
|
+
populate,
|
|
983
|
+
status: action.type === "publish" ? "draft" : "published"
|
|
984
|
+
},
|
|
985
|
+
{ strapi: strapi2 }
|
|
986
|
+
);
|
|
987
|
+
return {
|
|
988
|
+
...action,
|
|
989
|
+
entry,
|
|
990
|
+
status: entry ? await getEntryStatus(action.contentType, entry) : null
|
|
991
|
+
};
|
|
992
|
+
});
|
|
993
|
+
return {
|
|
994
|
+
results: actionsWithEntry,
|
|
995
|
+
pagination
|
|
996
|
+
};
|
|
997
|
+
},
|
|
998
|
+
async groupActions(actions, groupBy) {
|
|
999
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
1000
|
+
if (!acc.includes(action.contentType)) {
|
|
1001
|
+
acc.push(action.contentType);
|
|
1002
|
+
}
|
|
1003
|
+
return acc;
|
|
1004
|
+
}, []);
|
|
1005
|
+
const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
|
|
1006
|
+
const allLocalesDictionary = await getLocalesDataForActions();
|
|
1007
|
+
const formattedData = actions.map((action) => {
|
|
1008
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
1009
|
+
return {
|
|
1010
|
+
...action,
|
|
1011
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
1012
|
+
contentType: {
|
|
1013
|
+
displayName,
|
|
1014
|
+
mainFieldValue: action.entry[mainField],
|
|
1015
|
+
uid: action.contentType
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
1018
|
+
});
|
|
1019
|
+
const groupName = getGroupName(groupBy);
|
|
1020
|
+
return _.groupBy(groupName)(formattedData);
|
|
1021
|
+
},
|
|
1022
|
+
getContentTypeModelsFromActions(actions) {
|
|
1023
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
1024
|
+
if (!acc.includes(action.contentType)) {
|
|
1025
|
+
acc.push(action.contentType);
|
|
1026
|
+
}
|
|
1027
|
+
return acc;
|
|
1028
|
+
}, []);
|
|
1029
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
1030
|
+
(acc, contentTypeUid) => {
|
|
1031
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
1032
|
+
return acc;
|
|
1033
|
+
},
|
|
1034
|
+
{}
|
|
1035
|
+
);
|
|
1036
|
+
return contentTypeModelsMap;
|
|
1037
|
+
},
|
|
1038
|
+
async countActions(query) {
|
|
1039
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
1040
|
+
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
|
|
1041
|
+
},
|
|
1042
|
+
async update(actionId, releaseId, update) {
|
|
1043
|
+
const action = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findOne({
|
|
1020
1044
|
where: {
|
|
1021
1045
|
id: actionId,
|
|
1022
1046
|
release: {
|
|
@@ -1025,17 +1049,42 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
1025
1049
|
$null: true
|
|
1026
1050
|
}
|
|
1027
1051
|
}
|
|
1028
|
-
}
|
|
1029
|
-
data: update
|
|
1052
|
+
}
|
|
1030
1053
|
});
|
|
1031
|
-
if (!
|
|
1054
|
+
if (!action) {
|
|
1032
1055
|
throw new errors.NotFoundError(
|
|
1033
1056
|
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1034
1057
|
);
|
|
1035
1058
|
}
|
|
1059
|
+
const actionStatus = update.type === "publish" ? await getDraftEntryValidStatus(
|
|
1060
|
+
{
|
|
1061
|
+
contentType: action.contentType,
|
|
1062
|
+
documentId: action.entryDocumentId,
|
|
1063
|
+
locale: action.locale
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
strapi: strapi2
|
|
1067
|
+
}
|
|
1068
|
+
) : true;
|
|
1069
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1070
|
+
where: {
|
|
1071
|
+
id: actionId,
|
|
1072
|
+
release: {
|
|
1073
|
+
id: releaseId,
|
|
1074
|
+
releasedAt: {
|
|
1075
|
+
$null: true
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
},
|
|
1079
|
+
data: {
|
|
1080
|
+
...update,
|
|
1081
|
+
isEntryValid: actionStatus
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1036
1085
|
return updatedAction;
|
|
1037
1086
|
},
|
|
1038
|
-
async
|
|
1087
|
+
async delete(actionId, releaseId) {
|
|
1039
1088
|
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
1040
1089
|
where: {
|
|
1041
1090
|
id: actionId,
|
|
@@ -1052,51 +1101,8 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
1052
1101
|
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1053
1102
|
);
|
|
1054
1103
|
}
|
|
1055
|
-
|
|
1104
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1056
1105
|
return deletedAction;
|
|
1057
|
-
},
|
|
1058
|
-
async updateReleaseStatus(releaseId) {
|
|
1059
|
-
const [totalActions, invalidActions] = await Promise.all([
|
|
1060
|
-
this.countActions({
|
|
1061
|
-
filters: {
|
|
1062
|
-
release: releaseId
|
|
1063
|
-
}
|
|
1064
|
-
}),
|
|
1065
|
-
this.countActions({
|
|
1066
|
-
filters: {
|
|
1067
|
-
release: releaseId,
|
|
1068
|
-
isEntryValid: false
|
|
1069
|
-
}
|
|
1070
|
-
})
|
|
1071
|
-
]);
|
|
1072
|
-
if (totalActions > 0) {
|
|
1073
|
-
if (invalidActions > 0) {
|
|
1074
|
-
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1075
|
-
where: {
|
|
1076
|
-
id: releaseId
|
|
1077
|
-
},
|
|
1078
|
-
data: {
|
|
1079
|
-
status: "blocked"
|
|
1080
|
-
}
|
|
1081
|
-
});
|
|
1082
|
-
}
|
|
1083
|
-
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1084
|
-
where: {
|
|
1085
|
-
id: releaseId
|
|
1086
|
-
},
|
|
1087
|
-
data: {
|
|
1088
|
-
status: "ready"
|
|
1089
|
-
}
|
|
1090
|
-
});
|
|
1091
|
-
}
|
|
1092
|
-
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1093
|
-
where: {
|
|
1094
|
-
id: releaseId
|
|
1095
|
-
},
|
|
1096
|
-
data: {
|
|
1097
|
-
status: "empty"
|
|
1098
|
-
}
|
|
1099
|
-
});
|
|
1100
1106
|
}
|
|
1101
1107
|
};
|
|
1102
1108
|
};
|
|
@@ -1112,30 +1118,35 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
1112
1118
|
where: {
|
|
1113
1119
|
id: releaseId
|
|
1114
1120
|
},
|
|
1115
|
-
populate: {
|
|
1121
|
+
populate: {
|
|
1122
|
+
actions: true
|
|
1123
|
+
}
|
|
1116
1124
|
});
|
|
1117
1125
|
if (!release2) {
|
|
1118
1126
|
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1119
1127
|
}
|
|
1120
1128
|
const isEntryInRelease = release2.actions.some(
|
|
1121
|
-
(action) =>
|
|
1129
|
+
(action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
|
|
1122
1130
|
);
|
|
1123
1131
|
if (isEntryInRelease) {
|
|
1124
1132
|
throw new AlreadyOnReleaseError(
|
|
1125
|
-
`Entry with
|
|
1133
|
+
`Entry with documentId ${releaseActionArgs.entryDocumentId}${releaseActionArgs.locale ? `( ${releaseActionArgs.locale})` : ""} and contentType ${releaseActionArgs.contentType} already exists in release with id ${releaseId}`
|
|
1126
1134
|
);
|
|
1127
1135
|
}
|
|
1128
1136
|
},
|
|
1129
|
-
|
|
1137
|
+
validateEntryData(contentTypeUid, entryDocumentId) {
|
|
1130
1138
|
const contentType = strapi2.contentType(contentTypeUid);
|
|
1131
1139
|
if (!contentType) {
|
|
1132
1140
|
throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
|
|
1133
1141
|
}
|
|
1134
|
-
if (!contentType
|
|
1142
|
+
if (!contentTypes$1.hasDraftAndPublish(contentType)) {
|
|
1135
1143
|
throw new errors.ValidationError(
|
|
1136
1144
|
`Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
|
|
1137
1145
|
);
|
|
1138
1146
|
}
|
|
1147
|
+
if (contentType.kind === "collectionType" && !entryDocumentId) {
|
|
1148
|
+
throw new errors.ValidationError("Document id is required for collection type");
|
|
1149
|
+
}
|
|
1139
1150
|
},
|
|
1140
1151
|
async validatePendingReleasesLimit() {
|
|
1141
1152
|
const featureCfg = strapi2.ee.features.get("cms-content-releases");
|
|
@@ -1224,78 +1235,165 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
|
1224
1235
|
}
|
|
1225
1236
|
};
|
|
1226
1237
|
};
|
|
1238
|
+
const DEFAULT_SETTINGS = {
|
|
1239
|
+
defaultTimezone: null
|
|
1240
|
+
};
|
|
1241
|
+
const createSettingsService = ({ strapi: strapi2 }) => {
|
|
1242
|
+
const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
|
|
1243
|
+
return {
|
|
1244
|
+
async update({ settings: settings2 }) {
|
|
1245
|
+
const store = await getStore();
|
|
1246
|
+
store.set({ key: "settings", value: settings2 });
|
|
1247
|
+
return settings2;
|
|
1248
|
+
},
|
|
1249
|
+
async find() {
|
|
1250
|
+
const store = await getStore();
|
|
1251
|
+
const settings2 = await store.get({ key: "settings" });
|
|
1252
|
+
return {
|
|
1253
|
+
...DEFAULT_SETTINGS,
|
|
1254
|
+
...settings2 || {}
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
};
|
|
1227
1259
|
const services = {
|
|
1228
1260
|
release: createReleaseService,
|
|
1261
|
+
"release-action": createReleaseActionService,
|
|
1229
1262
|
"release-validation": createReleaseValidationService,
|
|
1230
|
-
scheduling: createSchedulingService
|
|
1263
|
+
scheduling: createSchedulingService,
|
|
1264
|
+
settings: createSettingsService
|
|
1231
1265
|
};
|
|
1232
|
-
const RELEASE_SCHEMA = yup.object().shape({
|
|
1233
|
-
name: yup.string().trim().required(),
|
|
1234
|
-
scheduledAt: yup.string().nullable(),
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
otherwise: yup.string().nullable()
|
|
1240
|
-
}),
|
|
1241
|
-
timezone: yup.string().when("isScheduled", {
|
|
1242
|
-
is: true,
|
|
1243
|
-
then: yup.string().required().nullable(),
|
|
1244
|
-
otherwise: yup.string().nullable()
|
|
1245
|
-
}),
|
|
1246
|
-
date: yup.string().when("isScheduled", {
|
|
1247
|
-
is: true,
|
|
1248
|
-
then: yup.string().required().nullable(),
|
|
1249
|
-
otherwise: yup.string().nullable()
|
|
1266
|
+
const RELEASE_SCHEMA = yup$1.object().shape({
|
|
1267
|
+
name: yup$1.string().trim().required(),
|
|
1268
|
+
scheduledAt: yup$1.string().nullable(),
|
|
1269
|
+
timezone: yup$1.string().when("scheduledAt", {
|
|
1270
|
+
is: (value) => value !== null && value !== void 0,
|
|
1271
|
+
then: yup$1.string().required(),
|
|
1272
|
+
otherwise: yup$1.string().nullable()
|
|
1250
1273
|
})
|
|
1251
1274
|
}).required().noUnknown();
|
|
1275
|
+
const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = yup$1.object().shape({
|
|
1276
|
+
contentType: yup$1.string().required(),
|
|
1277
|
+
entryDocumentId: yup$1.string().nullable(),
|
|
1278
|
+
hasEntryAttached: yup$1.string().nullable(),
|
|
1279
|
+
locale: yup$1.string().nullable()
|
|
1280
|
+
}).required().noUnknown();
|
|
1252
1281
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
1282
|
+
const validatefindByDocumentAttachedParams = validateYupSchema(
|
|
1283
|
+
FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
|
|
1284
|
+
);
|
|
1253
1285
|
const releaseController = {
|
|
1254
|
-
|
|
1286
|
+
/**
|
|
1287
|
+
* Find releases based on documents attached or not to the release.
|
|
1288
|
+
* If `hasEntryAttached` is true, it will return all releases that have the entry attached.
|
|
1289
|
+
* If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
|
|
1290
|
+
*/
|
|
1291
|
+
async findByDocumentAttached(ctx) {
|
|
1255
1292
|
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1256
1293
|
ability: ctx.state.userAbility,
|
|
1257
1294
|
model: RELEASE_MODEL_UID
|
|
1258
1295
|
});
|
|
1259
1296
|
await permissionsManager.validateQuery(ctx.query);
|
|
1260
1297
|
const releaseService = getService("release", { strapi });
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1298
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1299
|
+
await validatefindByDocumentAttachedParams(query);
|
|
1300
|
+
const model = strapi.getModel(query.contentType);
|
|
1301
|
+
if (model.kind && model.kind === "singleType") {
|
|
1302
|
+
const document = await strapi.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
1303
|
+
if (!document) {
|
|
1304
|
+
throw new errors.NotFoundError(`No entry found for contentType ${query.contentType}`);
|
|
1305
|
+
}
|
|
1306
|
+
query.entryDocumentId = document.documentId;
|
|
1307
|
+
}
|
|
1308
|
+
const { contentType, hasEntryAttached, entryDocumentId, locale } = query;
|
|
1309
|
+
const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
|
|
1310
|
+
if (isEntryAttached) {
|
|
1311
|
+
const releases = await releaseService.findMany({
|
|
1312
|
+
where: {
|
|
1313
|
+
releasedAt: null,
|
|
1314
|
+
actions: {
|
|
1315
|
+
contentType,
|
|
1316
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1317
|
+
locale: locale ?? null
|
|
1318
|
+
}
|
|
1319
|
+
},
|
|
1320
|
+
populate: {
|
|
1276
1321
|
actions: {
|
|
1277
|
-
|
|
1278
|
-
|
|
1322
|
+
fields: ["type"],
|
|
1323
|
+
filters: {
|
|
1324
|
+
contentType,
|
|
1325
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1326
|
+
locale: locale ?? null
|
|
1279
1327
|
}
|
|
1280
1328
|
}
|
|
1281
|
-
}
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
ctx.body = { data: releases };
|
|
1332
|
+
} else {
|
|
1333
|
+
const relatedReleases = await releaseService.findMany({
|
|
1334
|
+
where: {
|
|
1335
|
+
releasedAt: null,
|
|
1336
|
+
actions: {
|
|
1337
|
+
contentType,
|
|
1338
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1339
|
+
locale: locale ?? null
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1282
1342
|
});
|
|
1283
|
-
const
|
|
1343
|
+
const releases = await releaseService.findMany({
|
|
1284
1344
|
where: {
|
|
1345
|
+
$or: [
|
|
1346
|
+
{
|
|
1347
|
+
id: {
|
|
1348
|
+
$notIn: relatedReleases.map((release2) => release2.id)
|
|
1349
|
+
}
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
actions: null
|
|
1353
|
+
}
|
|
1354
|
+
],
|
|
1285
1355
|
releasedAt: null
|
|
1286
1356
|
}
|
|
1287
1357
|
});
|
|
1288
|
-
ctx.body = { data
|
|
1358
|
+
ctx.body = { data: releases };
|
|
1289
1359
|
}
|
|
1290
1360
|
},
|
|
1361
|
+
async findPage(ctx) {
|
|
1362
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1363
|
+
ability: ctx.state.userAbility,
|
|
1364
|
+
model: RELEASE_MODEL_UID
|
|
1365
|
+
});
|
|
1366
|
+
await permissionsManager.validateQuery(ctx.query);
|
|
1367
|
+
const releaseService = getService("release", { strapi });
|
|
1368
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1369
|
+
const { results, pagination } = await releaseService.findPage(query);
|
|
1370
|
+
const data = results.map((release2) => {
|
|
1371
|
+
const { actions, ...releaseData } = release2;
|
|
1372
|
+
return {
|
|
1373
|
+
...releaseData,
|
|
1374
|
+
actions: {
|
|
1375
|
+
meta: {
|
|
1376
|
+
count: actions.count
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
});
|
|
1381
|
+
const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
|
|
1382
|
+
where: {
|
|
1383
|
+
releasedAt: null
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
1387
|
+
},
|
|
1291
1388
|
async findOne(ctx) {
|
|
1292
1389
|
const id = ctx.params.id;
|
|
1293
1390
|
const releaseService = getService("release", { strapi });
|
|
1391
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1294
1392
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
1295
1393
|
if (!release2) {
|
|
1296
1394
|
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
1297
1395
|
}
|
|
1298
|
-
const count = await
|
|
1396
|
+
const count = await releaseActionService.countActions({
|
|
1299
1397
|
filters: {
|
|
1300
1398
|
release: id
|
|
1301
1399
|
}
|
|
@@ -1315,28 +1413,43 @@ const releaseController = {
|
|
|
1315
1413
|
ctx.body = { data };
|
|
1316
1414
|
},
|
|
1317
1415
|
async mapEntriesToReleases(ctx) {
|
|
1318
|
-
const { contentTypeUid,
|
|
1319
|
-
if (!contentTypeUid || !
|
|
1416
|
+
const { contentTypeUid, documentIds, locale } = ctx.query;
|
|
1417
|
+
if (!contentTypeUid || !documentIds) {
|
|
1320
1418
|
throw new errors.ValidationError("Missing required query parameters");
|
|
1321
1419
|
}
|
|
1322
1420
|
const releaseService = getService("release", { strapi });
|
|
1323
|
-
const releasesWithActions = await releaseService.
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1421
|
+
const releasesWithActions = await releaseService.findMany({
|
|
1422
|
+
where: {
|
|
1423
|
+
releasedAt: null,
|
|
1424
|
+
actions: {
|
|
1425
|
+
contentType: contentTypeUid,
|
|
1426
|
+
entryDocumentId: {
|
|
1427
|
+
$in: documentIds
|
|
1428
|
+
},
|
|
1429
|
+
locale
|
|
1430
|
+
}
|
|
1431
|
+
},
|
|
1432
|
+
populate: {
|
|
1433
|
+
actions: true
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1327
1436
|
const mappedEntriesInReleases = releasesWithActions.reduce(
|
|
1328
|
-
// TODO: Fix for v5 removed mappedEntriedToRelease
|
|
1329
1437
|
(acc, release2) => {
|
|
1330
1438
|
release2.actions.forEach((action) => {
|
|
1331
|
-
if (
|
|
1332
|
-
|
|
1439
|
+
if (action.contentType !== contentTypeUid) {
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
if (locale && action.locale !== locale) {
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
if (!acc[action.entryDocumentId]) {
|
|
1446
|
+
acc[action.entryDocumentId] = [{ id: release2.id, name: release2.name }];
|
|
1333
1447
|
} else {
|
|
1334
|
-
acc[action.
|
|
1448
|
+
acc[action.entryDocumentId].push({ id: release2.id, name: release2.name });
|
|
1335
1449
|
}
|
|
1336
1450
|
});
|
|
1337
1451
|
return acc;
|
|
1338
1452
|
},
|
|
1339
|
-
// TODO: Fix for v5 removed mappedEntriedToRelease
|
|
1340
1453
|
{}
|
|
1341
1454
|
);
|
|
1342
1455
|
ctx.body = {
|
|
@@ -1381,18 +1494,18 @@ const releaseController = {
|
|
|
1381
1494
|
};
|
|
1382
1495
|
},
|
|
1383
1496
|
async publish(ctx) {
|
|
1384
|
-
const user = ctx.state.user;
|
|
1385
1497
|
const id = ctx.params.id;
|
|
1386
1498
|
const releaseService = getService("release", { strapi });
|
|
1387
|
-
const
|
|
1499
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1500
|
+
const release2 = await releaseService.publish(id);
|
|
1388
1501
|
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1389
|
-
|
|
1502
|
+
releaseActionService.countActions({
|
|
1390
1503
|
filters: {
|
|
1391
1504
|
release: id,
|
|
1392
1505
|
type: "publish"
|
|
1393
1506
|
}
|
|
1394
1507
|
}),
|
|
1395
|
-
|
|
1508
|
+
releaseActionService.countActions({
|
|
1396
1509
|
filters: {
|
|
1397
1510
|
release: id,
|
|
1398
1511
|
type: "unpublish"
|
|
@@ -1410,24 +1523,27 @@ const releaseController = {
|
|
|
1410
1523
|
}
|
|
1411
1524
|
};
|
|
1412
1525
|
const RELEASE_ACTION_SCHEMA = yup$1.object().shape({
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
}).required(),
|
|
1526
|
+
contentType: yup$1.string().required(),
|
|
1527
|
+
entryDocumentId: yup$1.strapiID(),
|
|
1528
|
+
locale: yup$1.string(),
|
|
1417
1529
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
1418
1530
|
});
|
|
1419
1531
|
const RELEASE_ACTION_UPDATE_SCHEMA = yup$1.object().shape({
|
|
1420
1532
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
1421
1533
|
});
|
|
1534
|
+
const FIND_MANY_ACTIONS_PARAMS = yup$1.object().shape({
|
|
1535
|
+
groupBy: yup$1.string().oneOf(["action", "contentType", "locale"])
|
|
1536
|
+
});
|
|
1422
1537
|
const validateReleaseAction = validateYupSchema(RELEASE_ACTION_SCHEMA);
|
|
1423
1538
|
const validateReleaseActionUpdateSchema = validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
|
|
1539
|
+
const validateFindManyActionsParams = validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
|
|
1424
1540
|
const releaseActionController = {
|
|
1425
1541
|
async create(ctx) {
|
|
1426
1542
|
const releaseId = ctx.params.releaseId;
|
|
1427
1543
|
const releaseActionArgs = ctx.request.body;
|
|
1428
1544
|
await validateReleaseAction(releaseActionArgs);
|
|
1429
|
-
const
|
|
1430
|
-
const releaseAction2 = await
|
|
1545
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1546
|
+
const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
|
|
1431
1547
|
ctx.created({
|
|
1432
1548
|
data: releaseAction2
|
|
1433
1549
|
});
|
|
@@ -1438,12 +1554,15 @@ const releaseActionController = {
|
|
|
1438
1554
|
await Promise.all(
|
|
1439
1555
|
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
|
1440
1556
|
);
|
|
1557
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1441
1558
|
const releaseService = getService("release", { strapi });
|
|
1442
1559
|
const releaseActions = await strapi.db.transaction(async () => {
|
|
1443
1560
|
const releaseActions2 = await Promise.all(
|
|
1444
1561
|
releaseActionsArgs.map(async (releaseActionArgs) => {
|
|
1445
1562
|
try {
|
|
1446
|
-
const action = await
|
|
1563
|
+
const action = await releaseActionService.create(releaseId, releaseActionArgs, {
|
|
1564
|
+
disableUpdateReleaseStatus: true
|
|
1565
|
+
});
|
|
1447
1566
|
return action;
|
|
1448
1567
|
} catch (error) {
|
|
1449
1568
|
if (error instanceof AlreadyOnReleaseError) {
|
|
@@ -1456,6 +1575,9 @@ const releaseActionController = {
|
|
|
1456
1575
|
return releaseActions2;
|
|
1457
1576
|
});
|
|
1458
1577
|
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
|
1578
|
+
if (newReleaseActions.length > 0) {
|
|
1579
|
+
releaseService.updateReleaseStatus(releaseId);
|
|
1580
|
+
}
|
|
1459
1581
|
ctx.created({
|
|
1460
1582
|
data: newReleaseActions,
|
|
1461
1583
|
meta: {
|
|
@@ -1470,10 +1592,17 @@ const releaseActionController = {
|
|
|
1470
1592
|
ability: ctx.state.userAbility,
|
|
1471
1593
|
model: RELEASE_ACTION_MODEL_UID
|
|
1472
1594
|
});
|
|
1595
|
+
await validateFindManyActionsParams(ctx.query);
|
|
1596
|
+
if (ctx.query.groupBy) {
|
|
1597
|
+
if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
|
|
1598
|
+
ctx.badRequest("Invalid groupBy parameter");
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
|
|
1602
|
+
delete ctx.query.groupBy;
|
|
1473
1603
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1474
|
-
const
|
|
1475
|
-
const { results, pagination } = await
|
|
1476
|
-
sort: query.groupBy === "action" ? "type" : query.groupBy,
|
|
1604
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1605
|
+
const { results, pagination } = await releaseActionService.findPage(releaseId, {
|
|
1477
1606
|
...query
|
|
1478
1607
|
});
|
|
1479
1608
|
const contentTypeOutputSanitizers = results.reduce((acc, action) => {
|
|
@@ -1489,10 +1618,11 @@ const releaseActionController = {
|
|
|
1489
1618
|
}, {});
|
|
1490
1619
|
const sanitizedResults = await async.map(results, async (action) => ({
|
|
1491
1620
|
...action,
|
|
1492
|
-
entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
|
|
1621
|
+
entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
|
|
1493
1622
|
}));
|
|
1494
|
-
const groupedData = await
|
|
1495
|
-
const contentTypes2 =
|
|
1623
|
+
const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
|
|
1624
|
+
const contentTypes2 = releaseActionService.getContentTypeModelsFromActions(results);
|
|
1625
|
+
const releaseService = getService("release", { strapi });
|
|
1496
1626
|
const components = await releaseService.getAllComponents();
|
|
1497
1627
|
ctx.body = {
|
|
1498
1628
|
data: groupedData,
|
|
@@ -1508,8 +1638,8 @@ const releaseActionController = {
|
|
|
1508
1638
|
const releaseId = ctx.params.releaseId;
|
|
1509
1639
|
const releaseActionUpdateArgs = ctx.request.body;
|
|
1510
1640
|
await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
|
|
1511
|
-
const
|
|
1512
|
-
const updatedAction = await
|
|
1641
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1642
|
+
const updatedAction = await releaseActionService.update(
|
|
1513
1643
|
actionId,
|
|
1514
1644
|
releaseId,
|
|
1515
1645
|
releaseActionUpdateArgs
|
|
@@ -1521,14 +1651,36 @@ const releaseActionController = {
|
|
|
1521
1651
|
async delete(ctx) {
|
|
1522
1652
|
const actionId = ctx.params.actionId;
|
|
1523
1653
|
const releaseId = ctx.params.releaseId;
|
|
1524
|
-
const
|
|
1525
|
-
const deletedReleaseAction = await
|
|
1654
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1655
|
+
const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
|
|
1526
1656
|
ctx.body = {
|
|
1527
1657
|
data: deletedReleaseAction
|
|
1528
1658
|
};
|
|
1529
1659
|
}
|
|
1530
1660
|
};
|
|
1531
|
-
const
|
|
1661
|
+
const SETTINGS_SCHEMA = yup.object().shape({
|
|
1662
|
+
defaultTimezone: yup.string().nullable().default(null)
|
|
1663
|
+
}).required().noUnknown();
|
|
1664
|
+
const validateSettings = validateYupSchema(SETTINGS_SCHEMA);
|
|
1665
|
+
const settingsController = {
|
|
1666
|
+
async find(ctx) {
|
|
1667
|
+
const settingsService = getService("settings", { strapi });
|
|
1668
|
+
const settings2 = await settingsService.find();
|
|
1669
|
+
ctx.body = { data: settings2 };
|
|
1670
|
+
},
|
|
1671
|
+
async update(ctx) {
|
|
1672
|
+
const settingsBody = ctx.request.body;
|
|
1673
|
+
const settings2 = await validateSettings(settingsBody);
|
|
1674
|
+
const settingsService = getService("settings", { strapi });
|
|
1675
|
+
const updatedSettings = await settingsService.update({ settings: settings2 });
|
|
1676
|
+
ctx.body = { data: updatedSettings };
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1679
|
+
const controllers = {
|
|
1680
|
+
release: releaseController,
|
|
1681
|
+
"release-action": releaseActionController,
|
|
1682
|
+
settings: settingsController
|
|
1683
|
+
};
|
|
1532
1684
|
const release = {
|
|
1533
1685
|
type: "admin",
|
|
1534
1686
|
routes: [
|
|
@@ -1548,6 +1700,22 @@ const release = {
|
|
|
1548
1700
|
]
|
|
1549
1701
|
}
|
|
1550
1702
|
},
|
|
1703
|
+
{
|
|
1704
|
+
method: "GET",
|
|
1705
|
+
path: "/getByDocumentAttached",
|
|
1706
|
+
handler: "release.findByDocumentAttached",
|
|
1707
|
+
config: {
|
|
1708
|
+
policies: [
|
|
1709
|
+
"admin::isAuthenticatedAdmin",
|
|
1710
|
+
{
|
|
1711
|
+
name: "admin::hasPermissions",
|
|
1712
|
+
config: {
|
|
1713
|
+
actions: ["plugin::content-releases.read"]
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
]
|
|
1717
|
+
}
|
|
1718
|
+
},
|
|
1551
1719
|
{
|
|
1552
1720
|
method: "POST",
|
|
1553
1721
|
path: "/",
|
|
@@ -1567,7 +1735,7 @@ const release = {
|
|
|
1567
1735
|
{
|
|
1568
1736
|
method: "GET",
|
|
1569
1737
|
path: "/",
|
|
1570
|
-
handler: "release.
|
|
1738
|
+
handler: "release.findPage",
|
|
1571
1739
|
config: {
|
|
1572
1740
|
policies: [
|
|
1573
1741
|
"admin::isAuthenticatedAdmin",
|
|
@@ -1731,7 +1899,45 @@ const releaseAction = {
|
|
|
1731
1899
|
}
|
|
1732
1900
|
]
|
|
1733
1901
|
};
|
|
1902
|
+
const settings = {
|
|
1903
|
+
type: "admin",
|
|
1904
|
+
routes: [
|
|
1905
|
+
{
|
|
1906
|
+
method: "GET",
|
|
1907
|
+
path: "/settings",
|
|
1908
|
+
handler: "settings.find",
|
|
1909
|
+
config: {
|
|
1910
|
+
policies: [
|
|
1911
|
+
"admin::isAuthenticatedAdmin",
|
|
1912
|
+
{
|
|
1913
|
+
name: "admin::hasPermissions",
|
|
1914
|
+
config: {
|
|
1915
|
+
actions: ["plugin::content-releases.settings.read"]
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
]
|
|
1919
|
+
}
|
|
1920
|
+
},
|
|
1921
|
+
{
|
|
1922
|
+
method: "PUT",
|
|
1923
|
+
path: "/settings",
|
|
1924
|
+
handler: "settings.update",
|
|
1925
|
+
config: {
|
|
1926
|
+
policies: [
|
|
1927
|
+
"admin::isAuthenticatedAdmin",
|
|
1928
|
+
{
|
|
1929
|
+
name: "admin::hasPermissions",
|
|
1930
|
+
config: {
|
|
1931
|
+
actions: ["plugin::content-releases.settings.update"]
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
]
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
]
|
|
1938
|
+
};
|
|
1734
1939
|
const routes = {
|
|
1940
|
+
settings,
|
|
1735
1941
|
release,
|
|
1736
1942
|
"release-action": releaseAction
|
|
1737
1943
|
};
|