@strapi/content-releases 0.0.0-next.e6eaa3d0563c85f80fd88b258df70a55c057096e → 0.0.0-next.e8d8fc824d0f6a695b2a9ebaa4680ed21c3645ca
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-CiZCkScI.mjs +1558 -0
- package/dist/_chunks/App-CiZCkScI.mjs.map +1 -0
- package/dist/_chunks/App-SGjO5UPV.js +1578 -0
- package/dist/_chunks/App-SGjO5UPV.js.map +1 -0
- package/dist/_chunks/{PurchaseContentReleases-YhAPgpG9.js → PurchaseContentReleases--qQepXpP.js} +9 -8
- package/dist/_chunks/PurchaseContentReleases--qQepXpP.js.map +1 -0
- package/dist/_chunks/{PurchaseContentReleases-Clm0iACO.mjs → PurchaseContentReleases-D-n-w-st.mjs} +10 -9
- package/dist/_chunks/PurchaseContentReleases-D-n-w-st.mjs.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-Cto_NLUd.js +178 -0
- package/dist/_chunks/ReleasesSettingsPage-Cto_NLUd.js.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-DQT8N3A-.mjs +178 -0
- package/dist/_chunks/ReleasesSettingsPage-DQT8N3A-.mjs.map +1 -0
- package/dist/_chunks/{en-faJDuv3q.js → en-BWPPsSH-.js} +28 -3
- package/dist/_chunks/en-BWPPsSH-.js.map +1 -0
- package/dist/_chunks/{en-RdapH-9X.mjs → en-D9Q4YW03.mjs} +28 -3
- package/dist/_chunks/en-D9Q4YW03.mjs.map +1 -0
- package/dist/_chunks/index-BjvFfTtA.mjs +1386 -0
- package/dist/_chunks/index-BjvFfTtA.mjs.map +1 -0
- package/dist/_chunks/index-CyU534vL.js +1404 -0
- package/dist/_chunks/index-CyU534vL.js.map +1 -0
- package/dist/_chunks/schemas-DBYv9gK8.js +61 -0
- package/dist/_chunks/schemas-DBYv9gK8.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 -15
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +2 -16
- package/dist/admin/index.mjs.map +1 -1
- package/dist/admin/src/components/EntryValidationPopover.d.ts +13 -0
- package/dist/admin/src/components/RelativeTime.d.ts +28 -0
- package/dist/admin/src/components/ReleaseAction.d.ts +3 -0
- package/dist/admin/src/components/ReleaseActionMenu.d.ts +26 -0
- package/dist/admin/src/components/ReleaseActionModal.d.ts +24 -0
- package/dist/admin/src/components/ReleaseActionOptions.d.ts +9 -0
- package/dist/admin/src/components/ReleaseListCell.d.ts +28 -0
- package/dist/admin/src/components/ReleaseModal.d.ts +17 -0
- package/dist/admin/src/components/ReleasesPanel.d.ts +3 -0
- package/dist/admin/src/constants.d.ts +76 -0
- package/dist/admin/src/index.d.ts +3 -0
- package/dist/admin/src/modules/hooks.d.ts +7 -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/ReleasesSettingsPage.d.ts +1 -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/release.d.ts +112 -0
- package/dist/admin/src/store/hooks.d.ts +7 -0
- package/dist/admin/src/utils/api.d.ts +6 -0
- package/dist/admin/src/utils/prefixPluginTranslations.d.ts +3 -0
- package/dist/admin/src/utils/time.d.ts +10 -0
- package/dist/admin/src/validation/schemas.d.ts +6 -0
- package/dist/server/index.js +952 -618
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +952 -616
- 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 +21 -0
- package/dist/server/src/constants.d.ts.map +1 -0
- package/dist/server/src/content-types/index.d.ts +97 -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 +48 -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 +47 -0
- package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -0
- package/dist/server/src/controllers/index.d.ts +25 -0
- package/dist/server/src/controllers/index.d.ts.map +1 -0
- package/dist/server/src/controllers/release-action.d.ts +10 -0
- package/dist/server/src/controllers/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/release.d.ts +18 -0
- package/dist/server/src/controllers/release.d.ts.map +1 -0
- 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 +14 -0
- package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release.d.ts +4 -0
- package/dist/server/src/controllers/validation/release.d.ts.map +1 -0
- 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/destroy.d.ts +5 -0
- package/dist/server/src/destroy.d.ts.map +1 -0
- package/dist/server/src/index.d.ts +2111 -0
- package/dist/server/src/index.d.ts.map +1 -0
- 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 +13 -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 +51 -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/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 +1824 -0
- package/dist/server/src/services/index.d.ts.map +1 -0
- package/dist/server/src/services/release-action.d.ts +34 -0
- package/dist/server/src/services/release-action.d.ts.map +1 -0
- package/dist/server/src/services/release.d.ts +31 -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/settings.d.ts +13 -0
- package/dist/server/src/services/settings.d.ts.map +1 -0
- package/dist/server/src/services/validation.d.ts +18 -0
- package/dist/server/src/services/validation.d.ts.map +1 -0
- package/dist/server/src/utils/index.d.ts +35 -0
- package/dist/server/src/utils/index.d.ts.map +1 -0
- package/dist/shared/contracts/release-actions.d.ts +137 -0
- package/dist/shared/contracts/release-actions.d.ts.map +1 -0
- package/dist/shared/contracts/releases.d.ts +184 -0
- package/dist/shared/contracts/releases.d.ts.map +1 -0
- package/dist/shared/contracts/settings.d.ts +39 -0
- package/dist/shared/contracts/settings.d.ts.map +1 -0
- package/dist/shared/types.d.ts +24 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/package.json +35 -39
- package/dist/_chunks/App-R-kksTW7.mjs +0 -1308
- package/dist/_chunks/App-R-kksTW7.mjs.map +0 -1
- package/dist/_chunks/App-WZHc_d3m.js +0 -1331
- package/dist/_chunks/App-WZHc_d3m.js.map +0 -1
- package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +0 -1
- package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +0 -1
- package/dist/_chunks/en-RdapH-9X.mjs.map +0 -1
- package/dist/_chunks/en-faJDuv3q.js.map +0 -1
- package/dist/_chunks/index-k6fw6RYi.js +0 -1033
- package/dist/_chunks/index-k6fw6RYi.js.map +0 -1
- package/dist/_chunks/index-vpSczx8v.mjs +0 -1012
- package/dist/_chunks/index-vpSczx8v.mjs.map +0 -1
- package/strapi-server.js +0 -3
package/dist/server/index.js
CHANGED
|
@@ -3,13 +3,11 @@ const utils = require("@strapi/utils");
|
|
|
3
3
|
const isEqual = require("lodash/isEqual");
|
|
4
4
|
const lodash = require("lodash");
|
|
5
5
|
const _ = require("lodash/fp");
|
|
6
|
-
const EE = require("@strapi/strapi/dist/utils/ee");
|
|
7
6
|
const nodeSchedule = require("node-schedule");
|
|
8
7
|
const yup = require("yup");
|
|
9
8
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
10
9
|
function _interopNamespace(e) {
|
|
11
|
-
if (e && e.__esModule)
|
|
12
|
-
return e;
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
13
11
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
14
12
|
if (e) {
|
|
15
13
|
for (const k in e) {
|
|
@@ -27,7 +25,6 @@ function _interopNamespace(e) {
|
|
|
27
25
|
}
|
|
28
26
|
const isEqual__default = /* @__PURE__ */ _interopDefault(isEqual);
|
|
29
27
|
const ___default = /* @__PURE__ */ _interopDefault(_);
|
|
30
|
-
const EE__default = /* @__PURE__ */ _interopDefault(EE);
|
|
31
28
|
const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
|
|
32
29
|
const RELEASE_MODEL_UID = "plugin::content-releases.release";
|
|
33
30
|
const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
|
|
@@ -73,21 +70,38 @@ const ACTIONS = [
|
|
|
73
70
|
displayName: "Add an entry to a release",
|
|
74
71
|
uid: "create-action",
|
|
75
72
|
pluginName: "content-releases"
|
|
73
|
+
},
|
|
74
|
+
// Settings
|
|
75
|
+
{
|
|
76
|
+
uid: "settings.read",
|
|
77
|
+
section: "settings",
|
|
78
|
+
displayName: "Read",
|
|
79
|
+
category: "content releases",
|
|
80
|
+
subCategory: "options",
|
|
81
|
+
pluginName: "content-releases"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
uid: "settings.update",
|
|
85
|
+
section: "settings",
|
|
86
|
+
displayName: "Edit",
|
|
87
|
+
category: "content releases",
|
|
88
|
+
subCategory: "options",
|
|
89
|
+
pluginName: "content-releases"
|
|
76
90
|
}
|
|
77
91
|
];
|
|
78
92
|
const ALLOWED_WEBHOOK_EVENTS = {
|
|
79
93
|
RELEASES_PUBLISH: "releases.publish"
|
|
80
94
|
};
|
|
81
|
-
const getService = (name, { strapi: strapi2 }
|
|
95
|
+
const getService = (name, { strapi: strapi2 }) => {
|
|
82
96
|
return strapi2.plugin("content-releases").service(name);
|
|
83
97
|
};
|
|
84
|
-
const
|
|
98
|
+
const getDraftEntryValidStatus = async ({ contentType, documentId, locale }, { strapi: strapi2 }) => {
|
|
85
99
|
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
86
|
-
const populate = await populateBuilderService(
|
|
87
|
-
const entry = await
|
|
88
|
-
return entry;
|
|
100
|
+
const populate = await populateBuilderService(contentType).populateDeep(Infinity).build();
|
|
101
|
+
const entry = await getEntry({ contentType, documentId, locale, populate }, { strapi: strapi2 });
|
|
102
|
+
return isEntryValid(contentType, entry, { strapi: strapi2 });
|
|
89
103
|
};
|
|
90
|
-
const
|
|
104
|
+
const isEntryValid = async (contentTypeUid, entry, { strapi: strapi2 }) => {
|
|
91
105
|
try {
|
|
92
106
|
await strapi2.entityValidator.validateEntityCreation(
|
|
93
107
|
strapi2.getModel(contentTypeUid),
|
|
@@ -96,11 +110,54 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } =
|
|
|
96
110
|
// @ts-expect-error - FIXME: entity here is unnecessary
|
|
97
111
|
entry
|
|
98
112
|
);
|
|
113
|
+
const workflowsService = strapi2.plugin("review-workflows").service("workflows");
|
|
114
|
+
const workflow = await workflowsService.getAssignedWorkflow(contentTypeUid, {
|
|
115
|
+
populate: "stageRequiredToPublish"
|
|
116
|
+
});
|
|
117
|
+
if (workflow?.stageRequiredToPublish) {
|
|
118
|
+
return entry.strapi_stage.id === workflow.stageRequiredToPublish.id;
|
|
119
|
+
}
|
|
99
120
|
return true;
|
|
100
121
|
} catch {
|
|
101
122
|
return false;
|
|
102
123
|
}
|
|
103
124
|
};
|
|
125
|
+
const getEntry = async ({
|
|
126
|
+
contentType,
|
|
127
|
+
documentId,
|
|
128
|
+
locale,
|
|
129
|
+
populate,
|
|
130
|
+
status = "draft"
|
|
131
|
+
}, { strapi: strapi2 }) => {
|
|
132
|
+
if (documentId) {
|
|
133
|
+
const entry = await strapi2.documents(contentType).findOne({ documentId, locale, populate, status });
|
|
134
|
+
if (status === "published" && !entry) {
|
|
135
|
+
return strapi2.documents(contentType).findOne({ documentId, locale, populate, status: "draft" });
|
|
136
|
+
}
|
|
137
|
+
return entry;
|
|
138
|
+
}
|
|
139
|
+
return strapi2.documents(contentType).findFirst({ locale, populate, status });
|
|
140
|
+
};
|
|
141
|
+
const getEntryStatus = async (contentType, entry) => {
|
|
142
|
+
if (entry.publishedAt) {
|
|
143
|
+
return "published";
|
|
144
|
+
}
|
|
145
|
+
const publishedEntry = await strapi.documents(contentType).findOne({
|
|
146
|
+
documentId: entry.documentId,
|
|
147
|
+
locale: entry.locale,
|
|
148
|
+
status: "published",
|
|
149
|
+
fields: ["updatedAt"]
|
|
150
|
+
});
|
|
151
|
+
if (!publishedEntry) {
|
|
152
|
+
return "draft";
|
|
153
|
+
}
|
|
154
|
+
const entryUpdatedAt = new Date(entry.updatedAt).getTime();
|
|
155
|
+
const publishedEntryUpdatedAt = new Date(publishedEntry.updatedAt).getTime();
|
|
156
|
+
if (entryUpdatedAt > publishedEntryUpdatedAt) {
|
|
157
|
+
return "modified";
|
|
158
|
+
}
|
|
159
|
+
return "published";
|
|
160
|
+
};
|
|
104
161
|
async function deleteActionsOnDisableDraftAndPublish({
|
|
105
162
|
oldContentTypes,
|
|
106
163
|
contentTypes: contentTypes2
|
|
@@ -122,7 +179,7 @@ async function deleteActionsOnDisableDraftAndPublish({
|
|
|
122
179
|
async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
123
180
|
const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
|
|
124
181
|
if (deletedContentTypes.length) {
|
|
125
|
-
await utils.
|
|
182
|
+
await utils.async.map(deletedContentTypes, async (deletedContentTypeUID) => {
|
|
126
183
|
return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
|
|
127
184
|
});
|
|
128
185
|
}
|
|
@@ -141,25 +198,27 @@ async function migrateIsValidAndStatusReleases() {
|
|
|
141
198
|
}
|
|
142
199
|
}
|
|
143
200
|
});
|
|
144
|
-
utils.
|
|
201
|
+
utils.async.map(releasesWithoutStatus, async (release2) => {
|
|
145
202
|
const actions = release2.actions;
|
|
146
203
|
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
147
204
|
for (const action of notValidatedActions) {
|
|
148
205
|
if (action.entry) {
|
|
149
|
-
const
|
|
150
|
-
|
|
206
|
+
const isEntryValid2 = getDraftEntryValidStatus(
|
|
207
|
+
{
|
|
208
|
+
contentType: action.contentType,
|
|
209
|
+
documentId: action.entryDocumentId,
|
|
210
|
+
locale: action.locale
|
|
211
|
+
},
|
|
212
|
+
{ strapi }
|
|
213
|
+
);
|
|
214
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
215
|
+
where: {
|
|
216
|
+
id: action.id
|
|
217
|
+
},
|
|
218
|
+
data: {
|
|
219
|
+
isEntryValid: isEntryValid2
|
|
220
|
+
}
|
|
151
221
|
});
|
|
152
|
-
if (populatedEntry) {
|
|
153
|
-
const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
|
|
154
|
-
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
155
|
-
where: {
|
|
156
|
-
id: action.id
|
|
157
|
-
},
|
|
158
|
-
data: {
|
|
159
|
-
isEntryValid
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
222
|
}
|
|
164
223
|
}
|
|
165
224
|
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
@@ -172,7 +231,7 @@ async function migrateIsValidAndStatusReleases() {
|
|
|
172
231
|
}
|
|
173
232
|
}
|
|
174
233
|
});
|
|
175
|
-
utils.
|
|
234
|
+
utils.async.map(publishedReleases, async (release2) => {
|
|
176
235
|
return strapi.db.query(RELEASE_MODEL_UID).update({
|
|
177
236
|
where: {
|
|
178
237
|
id: release2.id
|
|
@@ -189,7 +248,7 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
|
|
|
189
248
|
(uid) => oldContentTypes[uid]?.options?.draftAndPublish
|
|
190
249
|
);
|
|
191
250
|
const releasesAffected = /* @__PURE__ */ new Set();
|
|
192
|
-
utils.
|
|
251
|
+
utils.async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
|
|
193
252
|
const oldContentType = oldContentTypes[contentTypeUID];
|
|
194
253
|
const contentType = contentTypes2[contentTypeUID];
|
|
195
254
|
if (!isEqual__default.default(oldContentType?.attributes, contentType?.attributes)) {
|
|
@@ -202,30 +261,30 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
|
|
|
202
261
|
release: true
|
|
203
262
|
}
|
|
204
263
|
});
|
|
205
|
-
await utils.
|
|
206
|
-
if (action.entry && action.release) {
|
|
207
|
-
const
|
|
208
|
-
|
|
264
|
+
await utils.async.map(actions, async (action) => {
|
|
265
|
+
if (action.entry && action.release && action.type === "publish") {
|
|
266
|
+
const isEntryValid2 = await getDraftEntryValidStatus(
|
|
267
|
+
{
|
|
268
|
+
contentType: contentTypeUID,
|
|
269
|
+
documentId: action.entryDocumentId,
|
|
270
|
+
locale: action.locale
|
|
271
|
+
},
|
|
272
|
+
{ strapi }
|
|
273
|
+
);
|
|
274
|
+
releasesAffected.add(action.release.id);
|
|
275
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
276
|
+
where: {
|
|
277
|
+
id: action.id
|
|
278
|
+
},
|
|
279
|
+
data: {
|
|
280
|
+
isEntryValid: isEntryValid2
|
|
281
|
+
}
|
|
209
282
|
});
|
|
210
|
-
if (populatedEntry) {
|
|
211
|
-
const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
|
|
212
|
-
strapi
|
|
213
|
-
});
|
|
214
|
-
releasesAffected.add(action.release.id);
|
|
215
|
-
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
216
|
-
where: {
|
|
217
|
-
id: action.id
|
|
218
|
-
},
|
|
219
|
-
data: {
|
|
220
|
-
isEntryValid
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
283
|
}
|
|
225
284
|
});
|
|
226
285
|
}
|
|
227
286
|
}).then(() => {
|
|
228
|
-
utils.
|
|
287
|
+
utils.async.map(releasesAffected, async (releaseId) => {
|
|
229
288
|
return getService("release", { strapi }).updateReleaseStatus(releaseId);
|
|
230
289
|
});
|
|
231
290
|
});
|
|
@@ -235,13 +294,16 @@ async function disableContentTypeLocalized({ oldContentTypes, contentTypes: cont
|
|
|
235
294
|
if (!oldContentTypes) {
|
|
236
295
|
return;
|
|
237
296
|
}
|
|
297
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
298
|
+
if (!i18nPlugin) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
238
301
|
for (const uid in contentTypes2) {
|
|
239
302
|
if (!oldContentTypes[uid]) {
|
|
240
303
|
continue;
|
|
241
304
|
}
|
|
242
305
|
const oldContentType = oldContentTypes[uid];
|
|
243
306
|
const contentType = contentTypes2[uid];
|
|
244
|
-
const i18nPlugin = strapi.plugin("i18n");
|
|
245
307
|
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
246
308
|
if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
|
|
247
309
|
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
@@ -254,13 +316,16 @@ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: conte
|
|
|
254
316
|
if (!oldContentTypes) {
|
|
255
317
|
return;
|
|
256
318
|
}
|
|
319
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
320
|
+
if (!i18nPlugin) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
257
323
|
for (const uid in contentTypes2) {
|
|
258
324
|
if (!oldContentTypes[uid]) {
|
|
259
325
|
continue;
|
|
260
326
|
}
|
|
261
327
|
const oldContentType = oldContentTypes[uid];
|
|
262
328
|
const contentType = contentTypes2[uid];
|
|
263
|
-
const i18nPlugin = strapi.plugin("i18n");
|
|
264
329
|
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
265
330
|
const { getDefaultLocale } = i18nPlugin.service("locales");
|
|
266
331
|
if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
|
|
@@ -271,11 +336,43 @@ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: conte
|
|
|
271
336
|
}
|
|
272
337
|
}
|
|
273
338
|
}
|
|
274
|
-
const
|
|
339
|
+
const addEntryDocumentToReleaseActions = {
|
|
340
|
+
name: "content-releases::5.0.0-add-entry-document-id-to-release-actions",
|
|
341
|
+
async up(trx, db) {
|
|
342
|
+
const hasTable = await trx.schema.hasTable("strapi_release_actions");
|
|
343
|
+
if (!hasTable) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const hasPolymorphicColumn = await trx.schema.hasColumn("strapi_release_actions", "target_id");
|
|
347
|
+
if (hasPolymorphicColumn) {
|
|
348
|
+
const hasEntryDocumentIdColumn = await trx.schema.hasColumn(
|
|
349
|
+
"strapi_release_actions",
|
|
350
|
+
"entry_document_id"
|
|
351
|
+
);
|
|
352
|
+
if (!hasEntryDocumentIdColumn) {
|
|
353
|
+
await trx.schema.alterTable("strapi_release_actions", (table) => {
|
|
354
|
+
table.string("entry_document_id");
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
const releaseActions = await trx.select("*").from("strapi_release_actions");
|
|
358
|
+
utils.async.map(releaseActions, async (action) => {
|
|
359
|
+
const { target_type, target_id } = action;
|
|
360
|
+
const entry = await db.query(target_type).findOne({ where: { id: target_id } });
|
|
361
|
+
if (entry) {
|
|
362
|
+
await trx("strapi_release_actions").update({ entry_document_id: entry.documentId }).where("id", action.id);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
async down() {
|
|
368
|
+
throw new Error("not implemented");
|
|
369
|
+
}
|
|
370
|
+
};
|
|
275
371
|
const register = async ({ strapi: strapi2 }) => {
|
|
276
|
-
if (features
|
|
277
|
-
await strapi2.admin
|
|
278
|
-
strapi2.
|
|
372
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
373
|
+
await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
|
|
374
|
+
strapi2.db.migrations.providers.internal.register(addEntryDocumentToReleaseActions);
|
|
375
|
+
strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
|
|
279
376
|
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
280
377
|
}
|
|
281
378
|
if (strapi2.plugin("graphql")) {
|
|
@@ -284,129 +381,135 @@ const register = async ({ strapi: strapi2 }) => {
|
|
|
284
381
|
graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
|
|
285
382
|
}
|
|
286
383
|
};
|
|
287
|
-
const
|
|
384
|
+
const updateActionsStatusAndUpdateReleaseStatus = async (contentType, entry) => {
|
|
385
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
386
|
+
where: {
|
|
387
|
+
releasedAt: null,
|
|
388
|
+
actions: {
|
|
389
|
+
contentType,
|
|
390
|
+
entryDocumentId: entry.documentId,
|
|
391
|
+
locale: entry.locale
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
const entryStatus = await isEntryValid(contentType, entry, { strapi });
|
|
396
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).updateMany({
|
|
397
|
+
where: {
|
|
398
|
+
contentType,
|
|
399
|
+
entryDocumentId: entry.documentId,
|
|
400
|
+
locale: entry.locale
|
|
401
|
+
},
|
|
402
|
+
data: {
|
|
403
|
+
isEntryValid: entryStatus
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
for (const release2 of releases) {
|
|
407
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const deleteActionsAndUpdateReleaseStatus = async (params) => {
|
|
411
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
412
|
+
where: {
|
|
413
|
+
actions: params
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
417
|
+
where: params
|
|
418
|
+
});
|
|
419
|
+
for (const release2 of releases) {
|
|
420
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
const deleteActionsOnDelete = async (ctx, next) => {
|
|
424
|
+
if (ctx.action !== "delete") {
|
|
425
|
+
return next();
|
|
426
|
+
}
|
|
427
|
+
if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
|
|
428
|
+
return next();
|
|
429
|
+
}
|
|
430
|
+
const contentType = ctx.contentType.uid;
|
|
431
|
+
const { documentId, locale } = ctx.params;
|
|
432
|
+
const result = await next();
|
|
433
|
+
if (!result) {
|
|
434
|
+
return result;
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
deleteActionsAndUpdateReleaseStatus({
|
|
438
|
+
contentType,
|
|
439
|
+
entryDocumentId: documentId,
|
|
440
|
+
...locale !== "*" && { locale }
|
|
441
|
+
});
|
|
442
|
+
} catch (error) {
|
|
443
|
+
strapi.log.error("Error while deleting release actions after delete", {
|
|
444
|
+
error
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
return result;
|
|
448
|
+
};
|
|
449
|
+
const updateActionsOnUpdate = async (ctx, next) => {
|
|
450
|
+
if (ctx.action !== "update") {
|
|
451
|
+
return next();
|
|
452
|
+
}
|
|
453
|
+
if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
|
|
454
|
+
return next();
|
|
455
|
+
}
|
|
456
|
+
const contentType = ctx.contentType.uid;
|
|
457
|
+
const result = await next();
|
|
458
|
+
if (!result) {
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
updateActionsStatusAndUpdateReleaseStatus(contentType, result);
|
|
463
|
+
} catch (error) {
|
|
464
|
+
strapi.log.error("Error while updating release actions after update", {
|
|
465
|
+
error
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
return result;
|
|
469
|
+
};
|
|
470
|
+
const deleteReleasesActionsAndUpdateReleaseStatus = async (params) => {
|
|
471
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
472
|
+
where: {
|
|
473
|
+
actions: params
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
477
|
+
where: params
|
|
478
|
+
});
|
|
479
|
+
for (const release2 of releases) {
|
|
480
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
288
483
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
289
|
-
if (features
|
|
484
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
290
485
|
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
291
486
|
(uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
|
|
292
487
|
);
|
|
293
488
|
strapi2.db.lifecycles.subscribe({
|
|
294
489
|
models: contentTypesWithDraftAndPublish,
|
|
295
|
-
async afterDelete(event) {
|
|
296
|
-
try {
|
|
297
|
-
const { model, result } = event;
|
|
298
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
299
|
-
const { id } = result;
|
|
300
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
301
|
-
where: {
|
|
302
|
-
actions: {
|
|
303
|
-
target_type: model.uid,
|
|
304
|
-
target_id: id
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
309
|
-
where: {
|
|
310
|
-
target_type: model.uid,
|
|
311
|
-
target_id: id
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
for (const release2 of releases) {
|
|
315
|
-
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
} catch (error) {
|
|
319
|
-
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
320
|
-
}
|
|
321
|
-
},
|
|
322
|
-
/**
|
|
323
|
-
* deleteMany hook doesn't return the deleted entries ids
|
|
324
|
-
* so we need to fetch them before deleting the entries to save the ids on our state
|
|
325
|
-
*/
|
|
326
|
-
async beforeDeleteMany(event) {
|
|
327
|
-
const { model, params } = event;
|
|
328
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
329
|
-
const { where } = params;
|
|
330
|
-
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
331
|
-
event.state.entriesToDelete = entriesToDelete;
|
|
332
|
-
}
|
|
333
|
-
},
|
|
334
490
|
/**
|
|
335
|
-
*
|
|
336
|
-
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
491
|
+
* deleteMany is still used outside documents service, for example when deleting a locale
|
|
337
492
|
*/
|
|
338
493
|
async afterDeleteMany(event) {
|
|
339
494
|
try {
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
target_id: {
|
|
348
|
-
$in: entriesToDelete.map(
|
|
349
|
-
(entry) => entry.id
|
|
350
|
-
)
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
356
|
-
where: {
|
|
357
|
-
target_type: model.uid,
|
|
358
|
-
target_id: {
|
|
359
|
-
$in: entriesToDelete.map((entry) => entry.id)
|
|
360
|
-
}
|
|
361
|
-
}
|
|
495
|
+
const model = strapi2.getModel(event.model.uid);
|
|
496
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
497
|
+
const { where } = event.params;
|
|
498
|
+
deleteReleasesActionsAndUpdateReleaseStatus({
|
|
499
|
+
contentType: model.uid,
|
|
500
|
+
locale: where?.locale ?? null,
|
|
501
|
+
...where?.documentId && { entryDocumentId: where.documentId }
|
|
362
502
|
});
|
|
363
|
-
for (const release2 of releases) {
|
|
364
|
-
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
365
|
-
}
|
|
366
503
|
}
|
|
367
504
|
} catch (error) {
|
|
368
505
|
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
369
506
|
error
|
|
370
507
|
});
|
|
371
508
|
}
|
|
372
|
-
},
|
|
373
|
-
async afterUpdate(event) {
|
|
374
|
-
try {
|
|
375
|
-
const { model, result } = event;
|
|
376
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
377
|
-
const isEntryValid = await getEntryValidStatus(
|
|
378
|
-
model.uid,
|
|
379
|
-
result,
|
|
380
|
-
{
|
|
381
|
-
strapi: strapi2
|
|
382
|
-
}
|
|
383
|
-
);
|
|
384
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
385
|
-
where: {
|
|
386
|
-
target_type: model.uid,
|
|
387
|
-
target_id: result.id
|
|
388
|
-
},
|
|
389
|
-
data: {
|
|
390
|
-
isEntryValid
|
|
391
|
-
}
|
|
392
|
-
});
|
|
393
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
394
|
-
where: {
|
|
395
|
-
actions: {
|
|
396
|
-
target_type: model.uid,
|
|
397
|
-
target_id: result.id
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
});
|
|
401
|
-
for (const release2 of releases) {
|
|
402
|
-
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
} catch (error) {
|
|
406
|
-
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
407
|
-
}
|
|
408
509
|
}
|
|
409
510
|
});
|
|
511
|
+
strapi2.documents.use(deleteActionsOnDelete);
|
|
512
|
+
strapi2.documents.use(updateActionsOnUpdate);
|
|
410
513
|
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
411
514
|
strapi2.log.error(
|
|
412
515
|
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
@@ -414,7 +517,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
414
517
|
throw err;
|
|
415
518
|
});
|
|
416
519
|
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
417
|
-
strapi2.webhookStore.addAllowedEvent(key, value);
|
|
520
|
+
strapi2.get("webhookStore").addAllowedEvent(key, value);
|
|
418
521
|
});
|
|
419
522
|
}
|
|
420
523
|
};
|
|
@@ -498,15 +601,13 @@ const schema = {
|
|
|
498
601
|
enum: ["publish", "unpublish"],
|
|
499
602
|
required: true
|
|
500
603
|
},
|
|
501
|
-
entry: {
|
|
502
|
-
type: "relation",
|
|
503
|
-
relation: "morphToOne",
|
|
504
|
-
configurable: false
|
|
505
|
-
},
|
|
506
604
|
contentType: {
|
|
507
605
|
type: "string",
|
|
508
606
|
required: true
|
|
509
607
|
},
|
|
608
|
+
entryDocumentId: {
|
|
609
|
+
type: "string"
|
|
610
|
+
},
|
|
510
611
|
locale: {
|
|
511
612
|
type: "string"
|
|
512
613
|
},
|
|
@@ -528,18 +629,6 @@ const contentTypes = {
|
|
|
528
629
|
release: release$1,
|
|
529
630
|
"release-action": releaseAction$1
|
|
530
631
|
};
|
|
531
|
-
const getGroupName = (queryValue) => {
|
|
532
|
-
switch (queryValue) {
|
|
533
|
-
case "contentType":
|
|
534
|
-
return "contentType.displayName";
|
|
535
|
-
case "action":
|
|
536
|
-
return "type";
|
|
537
|
-
case "locale":
|
|
538
|
-
return ___default.default.getOr("No locale", "locale.name");
|
|
539
|
-
default:
|
|
540
|
-
return "contentType.displayName";
|
|
541
|
-
}
|
|
542
|
-
};
|
|
543
632
|
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
544
633
|
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
545
634
|
strapi2.eventHub.emit(event, {
|
|
@@ -548,93 +637,32 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
548
637
|
release: release2
|
|
549
638
|
});
|
|
550
639
|
};
|
|
551
|
-
const publishSingleTypeAction = async (uid, actionType, entryId) => {
|
|
552
|
-
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
553
|
-
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
554
|
-
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
555
|
-
const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
|
|
556
|
-
try {
|
|
557
|
-
if (actionType === "publish") {
|
|
558
|
-
await entityManagerService.publish(entry, uid);
|
|
559
|
-
} else {
|
|
560
|
-
await entityManagerService.unpublish(entry, uid);
|
|
561
|
-
}
|
|
562
|
-
} catch (error) {
|
|
563
|
-
if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
564
|
-
;
|
|
565
|
-
else {
|
|
566
|
-
throw error;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
};
|
|
570
|
-
const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
|
|
571
|
-
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
572
|
-
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
573
|
-
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
574
|
-
const entriesToPublish = await strapi2.entityService.findMany(uid, {
|
|
575
|
-
filters: {
|
|
576
|
-
id: {
|
|
577
|
-
$in: entriesToPublishIds
|
|
578
|
-
}
|
|
579
|
-
},
|
|
580
|
-
populate
|
|
581
|
-
});
|
|
582
|
-
const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
|
|
583
|
-
filters: {
|
|
584
|
-
id: {
|
|
585
|
-
$in: entriestoUnpublishIds
|
|
586
|
-
}
|
|
587
|
-
},
|
|
588
|
-
populate
|
|
589
|
-
});
|
|
590
|
-
if (entriesToPublish.length > 0) {
|
|
591
|
-
await entityManagerService.publishMany(entriesToPublish, uid);
|
|
592
|
-
}
|
|
593
|
-
if (entriesToUnpublish.length > 0) {
|
|
594
|
-
await entityManagerService.unpublishMany(entriesToUnpublish, uid);
|
|
595
|
-
}
|
|
596
|
-
};
|
|
597
640
|
const getFormattedActions = async (releaseId) => {
|
|
598
641
|
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
599
642
|
where: {
|
|
600
643
|
release: {
|
|
601
644
|
id: releaseId
|
|
602
645
|
}
|
|
603
|
-
},
|
|
604
|
-
populate: {
|
|
605
|
-
entry: {
|
|
606
|
-
fields: ["id"]
|
|
607
|
-
}
|
|
608
646
|
}
|
|
609
647
|
});
|
|
610
648
|
if (actions.length === 0) {
|
|
611
649
|
throw new utils.errors.ValidationError("No entries to publish");
|
|
612
650
|
}
|
|
613
|
-
const
|
|
614
|
-
const singleTypeActions = [];
|
|
651
|
+
const formattedActions = {};
|
|
615
652
|
for (const action of actions) {
|
|
616
653
|
const contentTypeUid = action.contentType;
|
|
617
|
-
if (
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
if (action.type === "publish") {
|
|
625
|
-
collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
|
|
626
|
-
} else {
|
|
627
|
-
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
628
|
-
}
|
|
629
|
-
} else {
|
|
630
|
-
singleTypeActions.push({
|
|
631
|
-
uid: contentTypeUid,
|
|
632
|
-
action: action.type,
|
|
633
|
-
id: action.entry.id
|
|
634
|
-
});
|
|
654
|
+
if (!formattedActions[contentTypeUid]) {
|
|
655
|
+
formattedActions[contentTypeUid] = {
|
|
656
|
+
publish: [],
|
|
657
|
+
unpublish: []
|
|
658
|
+
};
|
|
635
659
|
}
|
|
660
|
+
formattedActions[contentTypeUid][action.type].push({
|
|
661
|
+
documentId: action.entryDocumentId,
|
|
662
|
+
locale: action.locale
|
|
663
|
+
});
|
|
636
664
|
}
|
|
637
|
-
return
|
|
665
|
+
return formattedActions;
|
|
638
666
|
};
|
|
639
667
|
return {
|
|
640
668
|
async create(releaseData, { user }) {
|
|
@@ -649,7 +677,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
649
677
|
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
650
678
|
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
651
679
|
]);
|
|
652
|
-
const release2 = await strapi2.
|
|
680
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).create({
|
|
653
681
|
data: {
|
|
654
682
|
...releaseWithCreatorFields,
|
|
655
683
|
status: "empty"
|
|
@@ -663,94 +691,28 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
663
691
|
return release2;
|
|
664
692
|
},
|
|
665
693
|
async findOne(id, query = {}) {
|
|
666
|
-
const
|
|
667
|
-
|
|
694
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query);
|
|
695
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
696
|
+
...dbQuery,
|
|
697
|
+
where: { id }
|
|
668
698
|
});
|
|
669
699
|
return release2;
|
|
670
700
|
},
|
|
671
701
|
findPage(query) {
|
|
672
|
-
|
|
673
|
-
|
|
702
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
|
|
703
|
+
return strapi2.db.query(RELEASE_MODEL_UID).findPage({
|
|
704
|
+
...dbQuery,
|
|
674
705
|
populate: {
|
|
675
706
|
actions: {
|
|
676
|
-
// @ts-expect-error Ignore missing properties
|
|
677
707
|
count: true
|
|
678
708
|
}
|
|
679
709
|
}
|
|
680
710
|
});
|
|
681
711
|
},
|
|
682
|
-
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
target_type: contentTypeUid,
|
|
687
|
-
target_id: entryId
|
|
688
|
-
},
|
|
689
|
-
releasedAt: {
|
|
690
|
-
$null: true
|
|
691
|
-
}
|
|
692
|
-
},
|
|
693
|
-
populate: {
|
|
694
|
-
// Filter the action to get only the content type entry
|
|
695
|
-
actions: {
|
|
696
|
-
where: {
|
|
697
|
-
target_type: contentTypeUid,
|
|
698
|
-
target_id: entryId
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
});
|
|
703
|
-
return releases.map((release2) => {
|
|
704
|
-
if (release2.actions?.length) {
|
|
705
|
-
const [actionForEntry] = release2.actions;
|
|
706
|
-
delete release2.actions;
|
|
707
|
-
return {
|
|
708
|
-
...release2,
|
|
709
|
-
action: actionForEntry
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
return release2;
|
|
713
|
-
});
|
|
714
|
-
},
|
|
715
|
-
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
716
|
-
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
717
|
-
where: {
|
|
718
|
-
releasedAt: {
|
|
719
|
-
$null: true
|
|
720
|
-
},
|
|
721
|
-
actions: {
|
|
722
|
-
target_type: contentTypeUid,
|
|
723
|
-
target_id: entryId
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
});
|
|
727
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
728
|
-
where: {
|
|
729
|
-
$or: [
|
|
730
|
-
{
|
|
731
|
-
id: {
|
|
732
|
-
$notIn: releasesRelated.map((release2) => release2.id)
|
|
733
|
-
}
|
|
734
|
-
},
|
|
735
|
-
{
|
|
736
|
-
actions: null
|
|
737
|
-
}
|
|
738
|
-
],
|
|
739
|
-
releasedAt: {
|
|
740
|
-
$null: true
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
});
|
|
744
|
-
return releases.map((release2) => {
|
|
745
|
-
if (release2.actions?.length) {
|
|
746
|
-
const [actionForEntry] = release2.actions;
|
|
747
|
-
delete release2.actions;
|
|
748
|
-
return {
|
|
749
|
-
...release2,
|
|
750
|
-
action: actionForEntry
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
return release2;
|
|
712
|
+
findMany(query) {
|
|
713
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
|
|
714
|
+
return strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
715
|
+
...dbQuery
|
|
754
716
|
});
|
|
755
717
|
},
|
|
756
718
|
async update(id, releaseData, { user }) {
|
|
@@ -765,19 +727,15 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
765
727
|
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
766
728
|
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
767
729
|
]);
|
|
768
|
-
const release2 = await strapi2.
|
|
730
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id } });
|
|
769
731
|
if (!release2) {
|
|
770
732
|
throw new utils.errors.NotFoundError(`No release found for id ${id}`);
|
|
771
733
|
}
|
|
772
734
|
if (release2.releasedAt) {
|
|
773
735
|
throw new utils.errors.ValidationError("Release already published");
|
|
774
736
|
}
|
|
775
|
-
const updatedRelease = await strapi2.
|
|
776
|
-
|
|
777
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
778
|
-
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
779
|
-
*/
|
|
780
|
-
// @ts-expect-error see above
|
|
737
|
+
const updatedRelease = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
738
|
+
where: { id },
|
|
781
739
|
data: releaseWithCreatorFields
|
|
782
740
|
});
|
|
783
741
|
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
@@ -790,130 +748,6 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
790
748
|
strapi2.telemetry.send("didUpdateContentRelease");
|
|
791
749
|
return updatedRelease;
|
|
792
750
|
},
|
|
793
|
-
async createAction(releaseId, action) {
|
|
794
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
795
|
-
strapi: strapi2
|
|
796
|
-
});
|
|
797
|
-
await Promise.all([
|
|
798
|
-
validateEntryContentType(action.entry.contentType),
|
|
799
|
-
validateUniqueEntry(releaseId, action)
|
|
800
|
-
]);
|
|
801
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
802
|
-
if (!release2) {
|
|
803
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
804
|
-
}
|
|
805
|
-
if (release2.releasedAt) {
|
|
806
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
807
|
-
}
|
|
808
|
-
const { entry, type } = action;
|
|
809
|
-
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
810
|
-
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
811
|
-
const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
812
|
-
data: {
|
|
813
|
-
type,
|
|
814
|
-
contentType: entry.contentType,
|
|
815
|
-
locale: entry.locale,
|
|
816
|
-
isEntryValid,
|
|
817
|
-
entry: {
|
|
818
|
-
id: entry.id,
|
|
819
|
-
__type: entry.contentType,
|
|
820
|
-
__pivot: { field: "entry" }
|
|
821
|
-
},
|
|
822
|
-
release: releaseId
|
|
823
|
-
},
|
|
824
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
825
|
-
});
|
|
826
|
-
this.updateReleaseStatus(releaseId);
|
|
827
|
-
return releaseAction2;
|
|
828
|
-
},
|
|
829
|
-
async findActions(releaseId, query) {
|
|
830
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
831
|
-
fields: ["id"]
|
|
832
|
-
});
|
|
833
|
-
if (!release2) {
|
|
834
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
835
|
-
}
|
|
836
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
837
|
-
...query,
|
|
838
|
-
populate: {
|
|
839
|
-
entry: {
|
|
840
|
-
populate: "*"
|
|
841
|
-
}
|
|
842
|
-
},
|
|
843
|
-
filters: {
|
|
844
|
-
release: releaseId
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
},
|
|
848
|
-
async countActions(query) {
|
|
849
|
-
return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
|
|
850
|
-
},
|
|
851
|
-
async groupActions(actions, groupBy) {
|
|
852
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
853
|
-
if (!acc.includes(action.contentType)) {
|
|
854
|
-
acc.push(action.contentType);
|
|
855
|
-
}
|
|
856
|
-
return acc;
|
|
857
|
-
}, []);
|
|
858
|
-
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
|
|
859
|
-
contentTypeUids
|
|
860
|
-
);
|
|
861
|
-
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
862
|
-
const formattedData = actions.map((action) => {
|
|
863
|
-
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
864
|
-
return {
|
|
865
|
-
...action,
|
|
866
|
-
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
867
|
-
contentType: {
|
|
868
|
-
displayName,
|
|
869
|
-
mainFieldValue: action.entry[mainField],
|
|
870
|
-
uid: action.contentType
|
|
871
|
-
}
|
|
872
|
-
};
|
|
873
|
-
});
|
|
874
|
-
const groupName = getGroupName(groupBy);
|
|
875
|
-
return ___default.default.groupBy(groupName)(formattedData);
|
|
876
|
-
},
|
|
877
|
-
async getLocalesDataForActions() {
|
|
878
|
-
if (!strapi2.plugin("i18n")) {
|
|
879
|
-
return {};
|
|
880
|
-
}
|
|
881
|
-
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
882
|
-
return allLocales.reduce((acc, locale) => {
|
|
883
|
-
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
884
|
-
return acc;
|
|
885
|
-
}, {});
|
|
886
|
-
},
|
|
887
|
-
async getContentTypesDataForActions(contentTypesUids) {
|
|
888
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
889
|
-
const contentTypesData = {};
|
|
890
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
891
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
892
|
-
uid: contentTypeUid
|
|
893
|
-
});
|
|
894
|
-
contentTypesData[contentTypeUid] = {
|
|
895
|
-
mainField: contentTypeConfig.settings.mainField,
|
|
896
|
-
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
return contentTypesData;
|
|
900
|
-
},
|
|
901
|
-
getContentTypeModelsFromActions(actions) {
|
|
902
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
903
|
-
if (!acc.includes(action.contentType)) {
|
|
904
|
-
acc.push(action.contentType);
|
|
905
|
-
}
|
|
906
|
-
return acc;
|
|
907
|
-
}, []);
|
|
908
|
-
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
909
|
-
(acc, contentTypeUid) => {
|
|
910
|
-
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
911
|
-
return acc;
|
|
912
|
-
},
|
|
913
|
-
{}
|
|
914
|
-
);
|
|
915
|
-
return contentTypeModelsMap;
|
|
916
|
-
},
|
|
917
751
|
async getAllComponents() {
|
|
918
752
|
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
919
753
|
const components = await contentManagerComponentsService.findAllComponents();
|
|
@@ -927,10 +761,11 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
927
761
|
return componentsMap;
|
|
928
762
|
},
|
|
929
763
|
async delete(releaseId) {
|
|
930
|
-
const release2 = await strapi2.
|
|
764
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
765
|
+
where: { id: releaseId },
|
|
931
766
|
populate: {
|
|
932
767
|
actions: {
|
|
933
|
-
|
|
768
|
+
select: ["id"]
|
|
934
769
|
}
|
|
935
770
|
}
|
|
936
771
|
});
|
|
@@ -948,7 +783,11 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
948
783
|
}
|
|
949
784
|
}
|
|
950
785
|
});
|
|
951
|
-
await strapi2.
|
|
786
|
+
await strapi2.db.query(RELEASE_MODEL_UID).delete({
|
|
787
|
+
where: {
|
|
788
|
+
id: releaseId
|
|
789
|
+
}
|
|
790
|
+
});
|
|
952
791
|
});
|
|
953
792
|
if (release2.scheduledAt) {
|
|
954
793
|
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
@@ -974,22 +813,19 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
974
813
|
}
|
|
975
814
|
try {
|
|
976
815
|
strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
|
|
977
|
-
const
|
|
978
|
-
|
|
816
|
+
const formattedActions = await getFormattedActions(releaseId);
|
|
817
|
+
await strapi2.db.transaction(
|
|
818
|
+
async () => Promise.all(
|
|
819
|
+
Object.keys(formattedActions).map(async (contentTypeUid) => {
|
|
820
|
+
const contentType = contentTypeUid;
|
|
821
|
+
const { publish, unpublish } = formattedActions[contentType];
|
|
822
|
+
return Promise.all([
|
|
823
|
+
...publish.map((params) => strapi2.documents(contentType).publish(params)),
|
|
824
|
+
...unpublish.map((params) => strapi2.documents(contentType).unpublish(params))
|
|
825
|
+
]);
|
|
826
|
+
})
|
|
827
|
+
)
|
|
979
828
|
);
|
|
980
|
-
await strapi2.db.transaction(async () => {
|
|
981
|
-
for (const { uid, action, id } of singleTypeActions) {
|
|
982
|
-
await publishSingleTypeAction(uid, action, id);
|
|
983
|
-
}
|
|
984
|
-
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
985
|
-
const uid = contentTypeUid;
|
|
986
|
-
await publishCollectionTypeAction(
|
|
987
|
-
uid,
|
|
988
|
-
collectionTypeActions[uid].entriesToPublishIds,
|
|
989
|
-
collectionTypeActions[uid].entriesToUnpublishIds
|
|
990
|
-
);
|
|
991
|
-
}
|
|
992
|
-
});
|
|
993
829
|
const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
994
830
|
where: {
|
|
995
831
|
id: releaseId
|
|
@@ -1010,22 +846,245 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
1010
846
|
isPublished: false,
|
|
1011
847
|
error: error2
|
|
1012
848
|
});
|
|
1013
|
-
await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
|
|
1014
|
-
status: "failed"
|
|
1015
|
-
}).transacting(trx).execute();
|
|
1016
|
-
return {
|
|
1017
|
-
release: null,
|
|
1018
|
-
error: error2
|
|
849
|
+
await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
|
|
850
|
+
status: "failed"
|
|
851
|
+
}).transacting(trx).execute();
|
|
852
|
+
return {
|
|
853
|
+
release: null,
|
|
854
|
+
error: error2
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
if (error instanceof Error) {
|
|
859
|
+
throw error;
|
|
860
|
+
}
|
|
861
|
+
return release2;
|
|
862
|
+
},
|
|
863
|
+
async updateReleaseStatus(releaseId) {
|
|
864
|
+
const releaseActionService = getService("release-action", { strapi: strapi2 });
|
|
865
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
866
|
+
releaseActionService.countActions({
|
|
867
|
+
filters: {
|
|
868
|
+
release: releaseId
|
|
869
|
+
}
|
|
870
|
+
}),
|
|
871
|
+
releaseActionService.countActions({
|
|
872
|
+
filters: {
|
|
873
|
+
release: releaseId,
|
|
874
|
+
isEntryValid: false
|
|
875
|
+
}
|
|
876
|
+
})
|
|
877
|
+
]);
|
|
878
|
+
if (totalActions > 0) {
|
|
879
|
+
if (invalidActions > 0) {
|
|
880
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
881
|
+
where: {
|
|
882
|
+
id: releaseId
|
|
883
|
+
},
|
|
884
|
+
data: {
|
|
885
|
+
status: "blocked"
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
890
|
+
where: {
|
|
891
|
+
id: releaseId
|
|
892
|
+
},
|
|
893
|
+
data: {
|
|
894
|
+
status: "ready"
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
899
|
+
where: {
|
|
900
|
+
id: releaseId
|
|
901
|
+
},
|
|
902
|
+
data: {
|
|
903
|
+
status: "empty"
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
};
|
|
909
|
+
const getGroupName = (queryValue) => {
|
|
910
|
+
switch (queryValue) {
|
|
911
|
+
case "contentType":
|
|
912
|
+
return "contentType.displayName";
|
|
913
|
+
case "type":
|
|
914
|
+
return "type";
|
|
915
|
+
case "locale":
|
|
916
|
+
return ___default.default.getOr("No locale", "locale.name");
|
|
917
|
+
default:
|
|
918
|
+
return "contentType.displayName";
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
const createReleaseActionService = ({ strapi: strapi2 }) => {
|
|
922
|
+
const getLocalesDataForActions = async () => {
|
|
923
|
+
if (!strapi2.plugin("i18n")) {
|
|
924
|
+
return {};
|
|
925
|
+
}
|
|
926
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
927
|
+
return allLocales.reduce((acc, locale) => {
|
|
928
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
929
|
+
return acc;
|
|
930
|
+
}, {});
|
|
931
|
+
};
|
|
932
|
+
const getContentTypesDataForActions = async (contentTypesUids) => {
|
|
933
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
934
|
+
const contentTypesData = {};
|
|
935
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
936
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
937
|
+
uid: contentTypeUid
|
|
938
|
+
});
|
|
939
|
+
contentTypesData[contentTypeUid] = {
|
|
940
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
941
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
return contentTypesData;
|
|
945
|
+
};
|
|
946
|
+
return {
|
|
947
|
+
async create(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
|
|
948
|
+
const { validateEntryData, validateUniqueEntry } = getService("release-validation", {
|
|
949
|
+
strapi: strapi2
|
|
950
|
+
});
|
|
951
|
+
await Promise.all([
|
|
952
|
+
validateEntryData(action.contentType, action.entryDocumentId),
|
|
953
|
+
validateUniqueEntry(releaseId, action)
|
|
954
|
+
]);
|
|
955
|
+
const model = strapi2.contentType(action.contentType);
|
|
956
|
+
if (model.kind === "singleType") {
|
|
957
|
+
const document = await strapi2.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
958
|
+
if (!document) {
|
|
959
|
+
throw new utils.errors.NotFoundError(`No entry found for contentType ${action.contentType}`);
|
|
960
|
+
}
|
|
961
|
+
action.entryDocumentId = document.documentId;
|
|
962
|
+
}
|
|
963
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
|
|
964
|
+
if (!release2) {
|
|
965
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
966
|
+
}
|
|
967
|
+
if (release2.releasedAt) {
|
|
968
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
969
|
+
}
|
|
970
|
+
const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
|
|
971
|
+
{
|
|
972
|
+
contentType: action.contentType,
|
|
973
|
+
documentId: action.entryDocumentId,
|
|
974
|
+
locale: action.locale
|
|
975
|
+
},
|
|
976
|
+
{
|
|
977
|
+
strapi: strapi2
|
|
978
|
+
}
|
|
979
|
+
) : true;
|
|
980
|
+
const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
|
|
981
|
+
data: {
|
|
982
|
+
...action,
|
|
983
|
+
release: release2.id,
|
|
984
|
+
isEntryValid: actionStatus
|
|
985
|
+
},
|
|
986
|
+
populate: { release: { select: ["id"] } }
|
|
987
|
+
});
|
|
988
|
+
if (!disableUpdateReleaseStatus) {
|
|
989
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
990
|
+
}
|
|
991
|
+
return releaseAction2;
|
|
992
|
+
},
|
|
993
|
+
async findPage(releaseId, query) {
|
|
994
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
995
|
+
where: { id: releaseId },
|
|
996
|
+
select: ["id"]
|
|
997
|
+
});
|
|
998
|
+
if (!release2) {
|
|
999
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1000
|
+
}
|
|
1001
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
1002
|
+
const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
1003
|
+
...dbQuery,
|
|
1004
|
+
where: {
|
|
1005
|
+
release: releaseId
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
1008
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
1009
|
+
const actionsWithEntry = await utils.async.map(actions, async (action) => {
|
|
1010
|
+
const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
|
|
1011
|
+
const entry = await getEntry(
|
|
1012
|
+
{
|
|
1013
|
+
contentType: action.contentType,
|
|
1014
|
+
documentId: action.entryDocumentId,
|
|
1015
|
+
locale: action.locale,
|
|
1016
|
+
populate,
|
|
1017
|
+
status: action.type === "publish" ? "draft" : "published"
|
|
1018
|
+
},
|
|
1019
|
+
{ strapi: strapi2 }
|
|
1020
|
+
);
|
|
1021
|
+
return {
|
|
1022
|
+
...action,
|
|
1023
|
+
entry,
|
|
1024
|
+
status: entry ? await getEntryStatus(action.contentType, entry) : null
|
|
1025
|
+
};
|
|
1026
|
+
});
|
|
1027
|
+
return {
|
|
1028
|
+
results: actionsWithEntry,
|
|
1029
|
+
pagination
|
|
1030
|
+
};
|
|
1031
|
+
},
|
|
1032
|
+
async groupActions(actions, groupBy) {
|
|
1033
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
1034
|
+
if (!acc.includes(action.contentType)) {
|
|
1035
|
+
acc.push(action.contentType);
|
|
1036
|
+
}
|
|
1037
|
+
return acc;
|
|
1038
|
+
}, []);
|
|
1039
|
+
const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
|
|
1040
|
+
const allLocalesDictionary = await getLocalesDataForActions();
|
|
1041
|
+
const formattedData = actions.map((action) => {
|
|
1042
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
1043
|
+
return {
|
|
1044
|
+
...action,
|
|
1045
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
1046
|
+
contentType: {
|
|
1047
|
+
displayName,
|
|
1048
|
+
mainFieldValue: action.entry[mainField],
|
|
1049
|
+
uid: action.contentType
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
});
|
|
1053
|
+
const groupName = getGroupName(groupBy);
|
|
1054
|
+
return ___default.default.groupBy(groupName)(formattedData);
|
|
1055
|
+
},
|
|
1056
|
+
async getContentTypeModelsFromActions(actions) {
|
|
1057
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
1058
|
+
if (!acc.includes(action.contentType)) {
|
|
1059
|
+
acc.push(action.contentType);
|
|
1060
|
+
}
|
|
1061
|
+
return acc;
|
|
1062
|
+
}, []);
|
|
1063
|
+
const workflowsService = strapi2.plugin("review-workflows").service("workflows");
|
|
1064
|
+
const contentTypeModelsMap = await utils.async.reduce(contentTypeUids)(
|
|
1065
|
+
async (accPromise, contentTypeUid) => {
|
|
1066
|
+
const acc = await accPromise;
|
|
1067
|
+
const contentTypeModel = strapi2.getModel(contentTypeUid);
|
|
1068
|
+
const workflow = await workflowsService.getAssignedWorkflow(contentTypeUid, {
|
|
1069
|
+
populate: "stageRequiredToPublish"
|
|
1070
|
+
});
|
|
1071
|
+
acc[contentTypeUid] = {
|
|
1072
|
+
...contentTypeModel,
|
|
1073
|
+
hasReviewWorkflow: !!workflow,
|
|
1074
|
+
stageRequiredToPublish: workflow?.stageRequiredToPublish
|
|
1019
1075
|
};
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
return release2;
|
|
1076
|
+
return acc;
|
|
1077
|
+
},
|
|
1078
|
+
{}
|
|
1079
|
+
);
|
|
1080
|
+
return contentTypeModelsMap;
|
|
1026
1081
|
},
|
|
1027
|
-
async
|
|
1028
|
-
const
|
|
1082
|
+
async countActions(query) {
|
|
1083
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
1084
|
+
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
|
|
1085
|
+
},
|
|
1086
|
+
async update(actionId, releaseId, update) {
|
|
1087
|
+
const action = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findOne({
|
|
1029
1088
|
where: {
|
|
1030
1089
|
id: actionId,
|
|
1031
1090
|
release: {
|
|
@@ -1034,17 +1093,42 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
1034
1093
|
$null: true
|
|
1035
1094
|
}
|
|
1036
1095
|
}
|
|
1037
|
-
}
|
|
1038
|
-
data: update
|
|
1096
|
+
}
|
|
1039
1097
|
});
|
|
1040
|
-
if (!
|
|
1098
|
+
if (!action) {
|
|
1041
1099
|
throw new utils.errors.NotFoundError(
|
|
1042
1100
|
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1043
1101
|
);
|
|
1044
1102
|
}
|
|
1103
|
+
const actionStatus = update.type === "publish" ? await getDraftEntryValidStatus(
|
|
1104
|
+
{
|
|
1105
|
+
contentType: action.contentType,
|
|
1106
|
+
documentId: action.entryDocumentId,
|
|
1107
|
+
locale: action.locale
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
strapi: strapi2
|
|
1111
|
+
}
|
|
1112
|
+
) : true;
|
|
1113
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1114
|
+
where: {
|
|
1115
|
+
id: actionId,
|
|
1116
|
+
release: {
|
|
1117
|
+
id: releaseId,
|
|
1118
|
+
releasedAt: {
|
|
1119
|
+
$null: true
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1123
|
+
data: {
|
|
1124
|
+
...update,
|
|
1125
|
+
isEntryValid: actionStatus
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1045
1129
|
return updatedAction;
|
|
1046
1130
|
},
|
|
1047
|
-
async
|
|
1131
|
+
async delete(actionId, releaseId) {
|
|
1048
1132
|
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
1049
1133
|
where: {
|
|
1050
1134
|
id: actionId,
|
|
@@ -1061,51 +1145,56 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
1061
1145
|
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1062
1146
|
);
|
|
1063
1147
|
}
|
|
1064
|
-
|
|
1148
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1065
1149
|
return deletedAction;
|
|
1066
1150
|
},
|
|
1067
|
-
async
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
}
|
|
1079
|
-
})
|
|
1080
|
-
]);
|
|
1081
|
-
if (totalActions > 0) {
|
|
1082
|
-
if (invalidActions > 0) {
|
|
1083
|
-
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1084
|
-
where: {
|
|
1085
|
-
id: releaseId
|
|
1086
|
-
},
|
|
1087
|
-
data: {
|
|
1088
|
-
status: "blocked"
|
|
1151
|
+
async validateActionsByContentTypes(contentTypeUids) {
|
|
1152
|
+
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
1153
|
+
where: {
|
|
1154
|
+
contentType: {
|
|
1155
|
+
$in: contentTypeUids
|
|
1156
|
+
},
|
|
1157
|
+
// We only want to validate actions that are going to be published
|
|
1158
|
+
type: "publish",
|
|
1159
|
+
release: {
|
|
1160
|
+
releasedAt: {
|
|
1161
|
+
$null: true
|
|
1089
1162
|
}
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1163
|
+
}
|
|
1164
|
+
},
|
|
1165
|
+
populate: { release: true }
|
|
1166
|
+
});
|
|
1167
|
+
const releasesUpdated = [];
|
|
1168
|
+
await utils.async.map(actions, async (action) => {
|
|
1169
|
+
const isValid = await getDraftEntryValidStatus(
|
|
1170
|
+
{
|
|
1171
|
+
contentType: action.contentType,
|
|
1172
|
+
documentId: action.entryDocumentId,
|
|
1173
|
+
locale: action.locale
|
|
1174
|
+
},
|
|
1175
|
+
{ strapi: strapi2 }
|
|
1176
|
+
);
|
|
1177
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1093
1178
|
where: {
|
|
1094
|
-
id:
|
|
1179
|
+
id: action.id
|
|
1095
1180
|
},
|
|
1096
1181
|
data: {
|
|
1097
|
-
|
|
1182
|
+
isEntryValid: isValid
|
|
1098
1183
|
}
|
|
1099
1184
|
});
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
where: {
|
|
1103
|
-
id: releaseId
|
|
1104
|
-
},
|
|
1105
|
-
data: {
|
|
1106
|
-
status: "empty"
|
|
1185
|
+
if (!releasesUpdated.includes(action.release.id)) {
|
|
1186
|
+
releasesUpdated.push(action.release.id);
|
|
1107
1187
|
}
|
|
1188
|
+
return {
|
|
1189
|
+
id: action.id,
|
|
1190
|
+
isEntryValid: isValid
|
|
1191
|
+
};
|
|
1108
1192
|
});
|
|
1193
|
+
if (releasesUpdated.length > 0) {
|
|
1194
|
+
await utils.async.map(releasesUpdated, async (releaseId) => {
|
|
1195
|
+
await getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1109
1198
|
}
|
|
1110
1199
|
};
|
|
1111
1200
|
};
|
|
@@ -1117,37 +1206,43 @@ class AlreadyOnReleaseError extends utils.errors.ApplicationError {
|
|
|
1117
1206
|
}
|
|
1118
1207
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
1119
1208
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
1120
|
-
const release2 = await strapi2.
|
|
1121
|
-
|
|
1209
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
1210
|
+
where: {
|
|
1211
|
+
id: releaseId
|
|
1212
|
+
},
|
|
1213
|
+
populate: {
|
|
1214
|
+
actions: true
|
|
1215
|
+
}
|
|
1122
1216
|
});
|
|
1123
1217
|
if (!release2) {
|
|
1124
1218
|
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1125
1219
|
}
|
|
1126
1220
|
const isEntryInRelease = release2.actions.some(
|
|
1127
|
-
(action) =>
|
|
1221
|
+
(action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
|
|
1128
1222
|
);
|
|
1129
1223
|
if (isEntryInRelease) {
|
|
1130
1224
|
throw new AlreadyOnReleaseError(
|
|
1131
|
-
`Entry with
|
|
1225
|
+
`Entry with documentId ${releaseActionArgs.entryDocumentId}${releaseActionArgs.locale ? `( ${releaseActionArgs.locale})` : ""} and contentType ${releaseActionArgs.contentType} already exists in release with id ${releaseId}`
|
|
1132
1226
|
);
|
|
1133
1227
|
}
|
|
1134
1228
|
},
|
|
1135
|
-
|
|
1229
|
+
validateEntryData(contentTypeUid, entryDocumentId) {
|
|
1136
1230
|
const contentType = strapi2.contentType(contentTypeUid);
|
|
1137
1231
|
if (!contentType) {
|
|
1138
1232
|
throw new utils.errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
|
|
1139
1233
|
}
|
|
1140
|
-
if (!contentType
|
|
1234
|
+
if (!utils.contentTypes.hasDraftAndPublish(contentType)) {
|
|
1141
1235
|
throw new utils.errors.ValidationError(
|
|
1142
1236
|
`Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
|
|
1143
1237
|
);
|
|
1144
1238
|
}
|
|
1239
|
+
if (contentType.kind === "collectionType" && !entryDocumentId) {
|
|
1240
|
+
throw new utils.errors.ValidationError("Document id is required for collection type");
|
|
1241
|
+
}
|
|
1145
1242
|
},
|
|
1146
1243
|
async validatePendingReleasesLimit() {
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
EE__default.default.features.get("cms-content-releases")?.options?.maximumReleases || 3
|
|
1150
|
-
);
|
|
1244
|
+
const featureCfg = strapi2.ee.features.get("cms-content-releases");
|
|
1245
|
+
const maximumPendingReleases = typeof featureCfg === "object" && featureCfg?.options?.maximumReleases || 3;
|
|
1151
1246
|
const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
|
|
1152
1247
|
filters: {
|
|
1153
1248
|
releasedAt: {
|
|
@@ -1160,8 +1255,8 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
1160
1255
|
}
|
|
1161
1256
|
},
|
|
1162
1257
|
async validateUniqueNameForPendingRelease(name, id) {
|
|
1163
|
-
const pendingReleases = await strapi2.
|
|
1164
|
-
|
|
1258
|
+
const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1259
|
+
where: {
|
|
1165
1260
|
releasedAt: {
|
|
1166
1261
|
$null: true
|
|
1167
1262
|
},
|
|
@@ -1190,7 +1285,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
|
1190
1285
|
}
|
|
1191
1286
|
const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
|
|
1192
1287
|
try {
|
|
1193
|
-
await getService("release").publish(releaseId);
|
|
1288
|
+
await getService("release", { strapi: strapi2 }).publish(releaseId);
|
|
1194
1289
|
} catch (error) {
|
|
1195
1290
|
}
|
|
1196
1291
|
this.cancel(releaseId);
|
|
@@ -1232,85 +1327,172 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
|
1232
1327
|
}
|
|
1233
1328
|
};
|
|
1234
1329
|
};
|
|
1330
|
+
const DEFAULT_SETTINGS = {
|
|
1331
|
+
defaultTimezone: null
|
|
1332
|
+
};
|
|
1333
|
+
const createSettingsService = ({ strapi: strapi2 }) => {
|
|
1334
|
+
const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
|
|
1335
|
+
return {
|
|
1336
|
+
async update({ settings: settings2 }) {
|
|
1337
|
+
const store = await getStore();
|
|
1338
|
+
store.set({ key: "settings", value: settings2 });
|
|
1339
|
+
return settings2;
|
|
1340
|
+
},
|
|
1341
|
+
async find() {
|
|
1342
|
+
const store = await getStore();
|
|
1343
|
+
const settings2 = await store.get({ key: "settings" });
|
|
1344
|
+
return {
|
|
1345
|
+
...DEFAULT_SETTINGS,
|
|
1346
|
+
...settings2 || {}
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
};
|
|
1235
1351
|
const services = {
|
|
1236
1352
|
release: createReleaseService,
|
|
1353
|
+
"release-action": createReleaseActionService,
|
|
1237
1354
|
"release-validation": createReleaseValidationService,
|
|
1238
|
-
scheduling: createSchedulingService
|
|
1355
|
+
scheduling: createSchedulingService,
|
|
1356
|
+
settings: createSettingsService
|
|
1239
1357
|
};
|
|
1240
|
-
const RELEASE_SCHEMA =
|
|
1241
|
-
name:
|
|
1242
|
-
scheduledAt:
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
otherwise: yup__namespace.string().nullable()
|
|
1248
|
-
}),
|
|
1249
|
-
timezone: yup__namespace.string().when("isScheduled", {
|
|
1250
|
-
is: true,
|
|
1251
|
-
then: yup__namespace.string().required().nullable(),
|
|
1252
|
-
otherwise: yup__namespace.string().nullable()
|
|
1253
|
-
}),
|
|
1254
|
-
date: yup__namespace.string().when("isScheduled", {
|
|
1255
|
-
is: true,
|
|
1256
|
-
then: yup__namespace.string().required().nullable(),
|
|
1257
|
-
otherwise: yup__namespace.string().nullable()
|
|
1358
|
+
const RELEASE_SCHEMA = utils.yup.object().shape({
|
|
1359
|
+
name: utils.yup.string().trim().required(),
|
|
1360
|
+
scheduledAt: utils.yup.string().nullable(),
|
|
1361
|
+
timezone: utils.yup.string().when("scheduledAt", {
|
|
1362
|
+
is: (value) => value !== null && value !== void 0,
|
|
1363
|
+
then: utils.yup.string().required(),
|
|
1364
|
+
otherwise: utils.yup.string().nullable()
|
|
1258
1365
|
})
|
|
1259
1366
|
}).required().noUnknown();
|
|
1367
|
+
const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = utils.yup.object().shape({
|
|
1368
|
+
contentType: utils.yup.string().required(),
|
|
1369
|
+
entryDocumentId: utils.yup.string().nullable(),
|
|
1370
|
+
hasEntryAttached: utils.yup.string().nullable(),
|
|
1371
|
+
locale: utils.yup.string().nullable()
|
|
1372
|
+
}).required().noUnknown();
|
|
1260
1373
|
const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
|
|
1374
|
+
const validatefindByDocumentAttachedParams = utils.validateYupSchema(
|
|
1375
|
+
FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
|
|
1376
|
+
);
|
|
1261
1377
|
const releaseController = {
|
|
1262
|
-
|
|
1263
|
-
|
|
1378
|
+
/**
|
|
1379
|
+
* Find releases based on documents attached or not to the release.
|
|
1380
|
+
* If `hasEntryAttached` is true, it will return all releases that have the entry attached.
|
|
1381
|
+
* If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
|
|
1382
|
+
*/
|
|
1383
|
+
async findByDocumentAttached(ctx) {
|
|
1384
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1264
1385
|
ability: ctx.state.userAbility,
|
|
1265
1386
|
model: RELEASE_MODEL_UID
|
|
1266
1387
|
});
|
|
1267
1388
|
await permissionsManager.validateQuery(ctx.query);
|
|
1268
1389
|
const releaseService = getService("release", { strapi });
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
const
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1390
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1391
|
+
await validatefindByDocumentAttachedParams(query);
|
|
1392
|
+
const model = strapi.getModel(query.contentType);
|
|
1393
|
+
if (model.kind && model.kind === "singleType") {
|
|
1394
|
+
const document = await strapi.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
1395
|
+
if (!document) {
|
|
1396
|
+
throw new utils.errors.NotFoundError(`No entry found for contentType ${query.contentType}`);
|
|
1397
|
+
}
|
|
1398
|
+
query.entryDocumentId = document.documentId;
|
|
1399
|
+
}
|
|
1400
|
+
const { contentType, hasEntryAttached, entryDocumentId, locale } = query;
|
|
1401
|
+
const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
|
|
1402
|
+
if (isEntryAttached) {
|
|
1403
|
+
const releases = await releaseService.findMany({
|
|
1404
|
+
where: {
|
|
1405
|
+
releasedAt: null,
|
|
1406
|
+
actions: {
|
|
1407
|
+
contentType,
|
|
1408
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1409
|
+
locale: locale ?? null
|
|
1410
|
+
}
|
|
1411
|
+
},
|
|
1412
|
+
populate: {
|
|
1284
1413
|
actions: {
|
|
1285
|
-
|
|
1286
|
-
|
|
1414
|
+
fields: ["type"],
|
|
1415
|
+
filters: {
|
|
1416
|
+
contentType,
|
|
1417
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1418
|
+
locale: locale ?? null
|
|
1287
1419
|
}
|
|
1288
1420
|
}
|
|
1289
|
-
}
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1423
|
+
ctx.body = { data: releases };
|
|
1424
|
+
} else {
|
|
1425
|
+
const relatedReleases = await releaseService.findMany({
|
|
1426
|
+
where: {
|
|
1427
|
+
releasedAt: null,
|
|
1428
|
+
actions: {
|
|
1429
|
+
contentType,
|
|
1430
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1431
|
+
locale: locale ?? null
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1290
1434
|
});
|
|
1291
|
-
const
|
|
1435
|
+
const releases = await releaseService.findMany({
|
|
1292
1436
|
where: {
|
|
1437
|
+
$or: [
|
|
1438
|
+
{
|
|
1439
|
+
id: {
|
|
1440
|
+
$notIn: relatedReleases.map((release2) => release2.id)
|
|
1441
|
+
}
|
|
1442
|
+
},
|
|
1443
|
+
{
|
|
1444
|
+
actions: null
|
|
1445
|
+
}
|
|
1446
|
+
],
|
|
1293
1447
|
releasedAt: null
|
|
1294
1448
|
}
|
|
1295
1449
|
});
|
|
1296
|
-
ctx.body = { data
|
|
1450
|
+
ctx.body = { data: releases };
|
|
1297
1451
|
}
|
|
1298
1452
|
},
|
|
1453
|
+
async findPage(ctx) {
|
|
1454
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1455
|
+
ability: ctx.state.userAbility,
|
|
1456
|
+
model: RELEASE_MODEL_UID
|
|
1457
|
+
});
|
|
1458
|
+
await permissionsManager.validateQuery(ctx.query);
|
|
1459
|
+
const releaseService = getService("release", { strapi });
|
|
1460
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1461
|
+
const { results, pagination } = await releaseService.findPage(query);
|
|
1462
|
+
const data = results.map((release2) => {
|
|
1463
|
+
const { actions, ...releaseData } = release2;
|
|
1464
|
+
return {
|
|
1465
|
+
...releaseData,
|
|
1466
|
+
actions: {
|
|
1467
|
+
meta: {
|
|
1468
|
+
count: actions.count
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
};
|
|
1472
|
+
});
|
|
1473
|
+
const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
|
|
1474
|
+
where: {
|
|
1475
|
+
releasedAt: null
|
|
1476
|
+
}
|
|
1477
|
+
});
|
|
1478
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
1479
|
+
},
|
|
1299
1480
|
async findOne(ctx) {
|
|
1300
1481
|
const id = ctx.params.id;
|
|
1301
1482
|
const releaseService = getService("release", { strapi });
|
|
1483
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1302
1484
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
1303
1485
|
if (!release2) {
|
|
1304
1486
|
throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
|
|
1305
1487
|
}
|
|
1306
|
-
const count = await
|
|
1488
|
+
const count = await releaseActionService.countActions({
|
|
1307
1489
|
filters: {
|
|
1308
1490
|
release: id
|
|
1309
1491
|
}
|
|
1310
1492
|
});
|
|
1311
1493
|
const sanitizedRelease = {
|
|
1312
1494
|
...release2,
|
|
1313
|
-
createdBy: release2.createdBy ? strapi.admin
|
|
1495
|
+
createdBy: release2.createdBy ? strapi.service("admin::user").sanitizeUser(release2.createdBy) : null
|
|
1314
1496
|
};
|
|
1315
1497
|
const data = {
|
|
1316
1498
|
...sanitizedRelease,
|
|
@@ -1322,19 +1504,63 @@ const releaseController = {
|
|
|
1322
1504
|
};
|
|
1323
1505
|
ctx.body = { data };
|
|
1324
1506
|
},
|
|
1507
|
+
async mapEntriesToReleases(ctx) {
|
|
1508
|
+
const { contentTypeUid, documentIds, locale } = ctx.query;
|
|
1509
|
+
if (!contentTypeUid || !documentIds) {
|
|
1510
|
+
throw new utils.errors.ValidationError("Missing required query parameters");
|
|
1511
|
+
}
|
|
1512
|
+
const releaseService = getService("release", { strapi });
|
|
1513
|
+
const releasesWithActions = await releaseService.findMany({
|
|
1514
|
+
where: {
|
|
1515
|
+
releasedAt: null,
|
|
1516
|
+
actions: {
|
|
1517
|
+
contentType: contentTypeUid,
|
|
1518
|
+
entryDocumentId: {
|
|
1519
|
+
$in: documentIds
|
|
1520
|
+
},
|
|
1521
|
+
locale
|
|
1522
|
+
}
|
|
1523
|
+
},
|
|
1524
|
+
populate: {
|
|
1525
|
+
actions: true
|
|
1526
|
+
}
|
|
1527
|
+
});
|
|
1528
|
+
const mappedEntriesInReleases = releasesWithActions.reduce(
|
|
1529
|
+
(acc, release2) => {
|
|
1530
|
+
release2.actions.forEach((action) => {
|
|
1531
|
+
if (action.contentType !== contentTypeUid) {
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
if (locale && action.locale !== locale) {
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
if (!acc[action.entryDocumentId]) {
|
|
1538
|
+
acc[action.entryDocumentId] = [{ id: release2.id, name: release2.name }];
|
|
1539
|
+
} else {
|
|
1540
|
+
acc[action.entryDocumentId].push({ id: release2.id, name: release2.name });
|
|
1541
|
+
}
|
|
1542
|
+
});
|
|
1543
|
+
return acc;
|
|
1544
|
+
},
|
|
1545
|
+
{}
|
|
1546
|
+
);
|
|
1547
|
+
ctx.body = {
|
|
1548
|
+
data: mappedEntriesInReleases
|
|
1549
|
+
};
|
|
1550
|
+
},
|
|
1325
1551
|
async create(ctx) {
|
|
1326
1552
|
const user = ctx.state.user;
|
|
1327
1553
|
const releaseArgs = ctx.request.body;
|
|
1328
1554
|
await validateRelease(releaseArgs);
|
|
1329
1555
|
const releaseService = getService("release", { strapi });
|
|
1330
1556
|
const release2 = await releaseService.create(releaseArgs, { user });
|
|
1331
|
-
const permissionsManager = strapi.admin
|
|
1557
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1332
1558
|
ability: ctx.state.userAbility,
|
|
1333
1559
|
model: RELEASE_MODEL_UID
|
|
1334
1560
|
});
|
|
1335
|
-
ctx.
|
|
1561
|
+
ctx.created({
|
|
1336
1562
|
data: await permissionsManager.sanitizeOutput(release2)
|
|
1337
|
-
};
|
|
1563
|
+
});
|
|
1338
1564
|
},
|
|
1339
1565
|
async update(ctx) {
|
|
1340
1566
|
const user = ctx.state.user;
|
|
@@ -1343,7 +1569,7 @@ const releaseController = {
|
|
|
1343
1569
|
await validateRelease(releaseArgs);
|
|
1344
1570
|
const releaseService = getService("release", { strapi });
|
|
1345
1571
|
const release2 = await releaseService.update(id, releaseArgs, { user });
|
|
1346
|
-
const permissionsManager = strapi.admin
|
|
1572
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1347
1573
|
ability: ctx.state.userAbility,
|
|
1348
1574
|
model: RELEASE_MODEL_UID
|
|
1349
1575
|
});
|
|
@@ -1360,18 +1586,18 @@ const releaseController = {
|
|
|
1360
1586
|
};
|
|
1361
1587
|
},
|
|
1362
1588
|
async publish(ctx) {
|
|
1363
|
-
const user = ctx.state.user;
|
|
1364
1589
|
const id = ctx.params.id;
|
|
1365
1590
|
const releaseService = getService("release", { strapi });
|
|
1366
|
-
const
|
|
1591
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1592
|
+
const release2 = await releaseService.publish(id);
|
|
1367
1593
|
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1368
|
-
|
|
1594
|
+
releaseActionService.countActions({
|
|
1369
1595
|
filters: {
|
|
1370
1596
|
release: id,
|
|
1371
1597
|
type: "publish"
|
|
1372
1598
|
}
|
|
1373
1599
|
}),
|
|
1374
|
-
|
|
1600
|
+
releaseActionService.countActions({
|
|
1375
1601
|
filters: {
|
|
1376
1602
|
release: id,
|
|
1377
1603
|
type: "unpublish"
|
|
@@ -1389,27 +1615,30 @@ const releaseController = {
|
|
|
1389
1615
|
}
|
|
1390
1616
|
};
|
|
1391
1617
|
const RELEASE_ACTION_SCHEMA = utils.yup.object().shape({
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
}).required(),
|
|
1618
|
+
contentType: utils.yup.string().required(),
|
|
1619
|
+
entryDocumentId: utils.yup.strapiID(),
|
|
1620
|
+
locale: utils.yup.string(),
|
|
1396
1621
|
type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
|
|
1397
1622
|
});
|
|
1398
1623
|
const RELEASE_ACTION_UPDATE_SCHEMA = utils.yup.object().shape({
|
|
1399
1624
|
type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
|
|
1400
1625
|
});
|
|
1626
|
+
const FIND_MANY_ACTIONS_PARAMS = utils.yup.object().shape({
|
|
1627
|
+
groupBy: utils.yup.string().oneOf(["action", "contentType", "locale"])
|
|
1628
|
+
});
|
|
1401
1629
|
const validateReleaseAction = utils.validateYupSchema(RELEASE_ACTION_SCHEMA);
|
|
1402
1630
|
const validateReleaseActionUpdateSchema = utils.validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
|
|
1631
|
+
const validateFindManyActionsParams = utils.validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
|
|
1403
1632
|
const releaseActionController = {
|
|
1404
1633
|
async create(ctx) {
|
|
1405
1634
|
const releaseId = ctx.params.releaseId;
|
|
1406
1635
|
const releaseActionArgs = ctx.request.body;
|
|
1407
1636
|
await validateReleaseAction(releaseActionArgs);
|
|
1408
|
-
const
|
|
1409
|
-
const releaseAction2 = await
|
|
1410
|
-
ctx.
|
|
1637
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1638
|
+
const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
|
|
1639
|
+
ctx.created({
|
|
1411
1640
|
data: releaseAction2
|
|
1412
|
-
};
|
|
1641
|
+
});
|
|
1413
1642
|
},
|
|
1414
1643
|
async createMany(ctx) {
|
|
1415
1644
|
const releaseId = ctx.params.releaseId;
|
|
@@ -1417,12 +1646,15 @@ const releaseActionController = {
|
|
|
1417
1646
|
await Promise.all(
|
|
1418
1647
|
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
|
1419
1648
|
);
|
|
1649
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1420
1650
|
const releaseService = getService("release", { strapi });
|
|
1421
1651
|
const releaseActions = await strapi.db.transaction(async () => {
|
|
1422
1652
|
const releaseActions2 = await Promise.all(
|
|
1423
1653
|
releaseActionsArgs.map(async (releaseActionArgs) => {
|
|
1424
1654
|
try {
|
|
1425
|
-
const action = await
|
|
1655
|
+
const action = await releaseActionService.create(releaseId, releaseActionArgs, {
|
|
1656
|
+
disableUpdateReleaseStatus: true
|
|
1657
|
+
});
|
|
1426
1658
|
return action;
|
|
1427
1659
|
} catch (error) {
|
|
1428
1660
|
if (error instanceof AlreadyOnReleaseError) {
|
|
@@ -1435,43 +1667,54 @@ const releaseActionController = {
|
|
|
1435
1667
|
return releaseActions2;
|
|
1436
1668
|
});
|
|
1437
1669
|
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
|
1438
|
-
|
|
1670
|
+
if (newReleaseActions.length > 0) {
|
|
1671
|
+
releaseService.updateReleaseStatus(releaseId);
|
|
1672
|
+
}
|
|
1673
|
+
ctx.created({
|
|
1439
1674
|
data: newReleaseActions,
|
|
1440
1675
|
meta: {
|
|
1441
1676
|
entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
|
|
1442
1677
|
totalEntries: releaseActions.length
|
|
1443
1678
|
}
|
|
1444
|
-
};
|
|
1679
|
+
});
|
|
1445
1680
|
},
|
|
1446
1681
|
async findMany(ctx) {
|
|
1447
1682
|
const releaseId = ctx.params.releaseId;
|
|
1448
|
-
const permissionsManager = strapi.admin
|
|
1683
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1449
1684
|
ability: ctx.state.userAbility,
|
|
1450
1685
|
model: RELEASE_ACTION_MODEL_UID
|
|
1451
1686
|
});
|
|
1687
|
+
await validateFindManyActionsParams(ctx.query);
|
|
1688
|
+
if (ctx.query.groupBy) {
|
|
1689
|
+
if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
|
|
1690
|
+
ctx.badRequest("Invalid groupBy parameter");
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
|
|
1694
|
+
delete ctx.query.groupBy;
|
|
1452
1695
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1453
|
-
const
|
|
1454
|
-
const { results, pagination } = await
|
|
1455
|
-
sort: query.groupBy === "action" ? "type" : query.groupBy,
|
|
1696
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1697
|
+
const { results, pagination } = await releaseActionService.findPage(releaseId, {
|
|
1456
1698
|
...query
|
|
1457
1699
|
});
|
|
1458
1700
|
const contentTypeOutputSanitizers = results.reduce((acc, action) => {
|
|
1459
1701
|
if (acc[action.contentType]) {
|
|
1460
1702
|
return acc;
|
|
1461
1703
|
}
|
|
1462
|
-
const contentTypePermissionsManager = strapi.admin
|
|
1704
|
+
const contentTypePermissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1463
1705
|
ability: ctx.state.userAbility,
|
|
1464
1706
|
model: action.contentType
|
|
1465
1707
|
});
|
|
1466
1708
|
acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
|
|
1467
1709
|
return acc;
|
|
1468
1710
|
}, {});
|
|
1469
|
-
const sanitizedResults = await utils.
|
|
1711
|
+
const sanitizedResults = await utils.async.map(results, async (action) => ({
|
|
1470
1712
|
...action,
|
|
1471
|
-
entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
|
|
1713
|
+
entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
|
|
1472
1714
|
}));
|
|
1473
|
-
const groupedData = await
|
|
1474
|
-
const contentTypes2 =
|
|
1715
|
+
const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
|
|
1716
|
+
const contentTypes2 = await releaseActionService.getContentTypeModelsFromActions(results);
|
|
1717
|
+
const releaseService = getService("release", { strapi });
|
|
1475
1718
|
const components = await releaseService.getAllComponents();
|
|
1476
1719
|
ctx.body = {
|
|
1477
1720
|
data: groupedData,
|
|
@@ -1487,8 +1730,8 @@ const releaseActionController = {
|
|
|
1487
1730
|
const releaseId = ctx.params.releaseId;
|
|
1488
1731
|
const releaseActionUpdateArgs = ctx.request.body;
|
|
1489
1732
|
await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
|
|
1490
|
-
const
|
|
1491
|
-
const updatedAction = await
|
|
1733
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1734
|
+
const updatedAction = await releaseActionService.update(
|
|
1492
1735
|
actionId,
|
|
1493
1736
|
releaseId,
|
|
1494
1737
|
releaseActionUpdateArgs
|
|
@@ -1500,17 +1743,71 @@ const releaseActionController = {
|
|
|
1500
1743
|
async delete(ctx) {
|
|
1501
1744
|
const actionId = ctx.params.actionId;
|
|
1502
1745
|
const releaseId = ctx.params.releaseId;
|
|
1503
|
-
const
|
|
1504
|
-
const deletedReleaseAction = await
|
|
1746
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1747
|
+
const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
|
|
1505
1748
|
ctx.body = {
|
|
1506
1749
|
data: deletedReleaseAction
|
|
1507
1750
|
};
|
|
1508
1751
|
}
|
|
1509
1752
|
};
|
|
1510
|
-
const
|
|
1753
|
+
const SETTINGS_SCHEMA = yup__namespace.object().shape({
|
|
1754
|
+
defaultTimezone: yup__namespace.string().nullable().default(null)
|
|
1755
|
+
}).required().noUnknown();
|
|
1756
|
+
const validateSettings = utils.validateYupSchema(SETTINGS_SCHEMA);
|
|
1757
|
+
const settingsController = {
|
|
1758
|
+
async find(ctx) {
|
|
1759
|
+
const settingsService = getService("settings", { strapi });
|
|
1760
|
+
const settings2 = await settingsService.find();
|
|
1761
|
+
ctx.body = { data: settings2 };
|
|
1762
|
+
},
|
|
1763
|
+
async update(ctx) {
|
|
1764
|
+
const settingsBody = ctx.request.body;
|
|
1765
|
+
const settings2 = await validateSettings(settingsBody);
|
|
1766
|
+
const settingsService = getService("settings", { strapi });
|
|
1767
|
+
const updatedSettings = await settingsService.update({ settings: settings2 });
|
|
1768
|
+
ctx.body = { data: updatedSettings };
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1771
|
+
const controllers = {
|
|
1772
|
+
release: releaseController,
|
|
1773
|
+
"release-action": releaseActionController,
|
|
1774
|
+
settings: settingsController
|
|
1775
|
+
};
|
|
1511
1776
|
const release = {
|
|
1512
1777
|
type: "admin",
|
|
1513
1778
|
routes: [
|
|
1779
|
+
{
|
|
1780
|
+
method: "GET",
|
|
1781
|
+
path: "/mapEntriesToReleases",
|
|
1782
|
+
handler: "release.mapEntriesToReleases",
|
|
1783
|
+
config: {
|
|
1784
|
+
policies: [
|
|
1785
|
+
"admin::isAuthenticatedAdmin",
|
|
1786
|
+
{
|
|
1787
|
+
name: "admin::hasPermissions",
|
|
1788
|
+
config: {
|
|
1789
|
+
actions: ["plugin::content-releases.read"]
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
]
|
|
1793
|
+
}
|
|
1794
|
+
},
|
|
1795
|
+
{
|
|
1796
|
+
method: "GET",
|
|
1797
|
+
path: "/getByDocumentAttached",
|
|
1798
|
+
handler: "release.findByDocumentAttached",
|
|
1799
|
+
config: {
|
|
1800
|
+
policies: [
|
|
1801
|
+
"admin::isAuthenticatedAdmin",
|
|
1802
|
+
{
|
|
1803
|
+
name: "admin::hasPermissions",
|
|
1804
|
+
config: {
|
|
1805
|
+
actions: ["plugin::content-releases.read"]
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
]
|
|
1809
|
+
}
|
|
1810
|
+
},
|
|
1514
1811
|
{
|
|
1515
1812
|
method: "POST",
|
|
1516
1813
|
path: "/",
|
|
@@ -1530,7 +1827,7 @@ const release = {
|
|
|
1530
1827
|
{
|
|
1531
1828
|
method: "GET",
|
|
1532
1829
|
path: "/",
|
|
1533
|
-
handler: "release.
|
|
1830
|
+
handler: "release.findPage",
|
|
1534
1831
|
config: {
|
|
1535
1832
|
policies: [
|
|
1536
1833
|
"admin::isAuthenticatedAdmin",
|
|
@@ -1694,13 +1991,50 @@ const releaseAction = {
|
|
|
1694
1991
|
}
|
|
1695
1992
|
]
|
|
1696
1993
|
};
|
|
1994
|
+
const settings = {
|
|
1995
|
+
type: "admin",
|
|
1996
|
+
routes: [
|
|
1997
|
+
{
|
|
1998
|
+
method: "GET",
|
|
1999
|
+
path: "/settings",
|
|
2000
|
+
handler: "settings.find",
|
|
2001
|
+
config: {
|
|
2002
|
+
policies: [
|
|
2003
|
+
"admin::isAuthenticatedAdmin",
|
|
2004
|
+
{
|
|
2005
|
+
name: "admin::hasPermissions",
|
|
2006
|
+
config: {
|
|
2007
|
+
actions: ["plugin::content-releases.settings.read"]
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
]
|
|
2011
|
+
}
|
|
2012
|
+
},
|
|
2013
|
+
{
|
|
2014
|
+
method: "PUT",
|
|
2015
|
+
path: "/settings",
|
|
2016
|
+
handler: "settings.update",
|
|
2017
|
+
config: {
|
|
2018
|
+
policies: [
|
|
2019
|
+
"admin::isAuthenticatedAdmin",
|
|
2020
|
+
{
|
|
2021
|
+
name: "admin::hasPermissions",
|
|
2022
|
+
config: {
|
|
2023
|
+
actions: ["plugin::content-releases.settings.update"]
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
]
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
]
|
|
2030
|
+
};
|
|
1697
2031
|
const routes = {
|
|
2032
|
+
settings,
|
|
1698
2033
|
release,
|
|
1699
2034
|
"release-action": releaseAction
|
|
1700
2035
|
};
|
|
1701
|
-
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
1702
2036
|
const getPlugin = () => {
|
|
1703
|
-
if (features.isEnabled("cms-content-releases")) {
|
|
2037
|
+
if (strapi.ee.features.isEnabled("cms-content-releases")) {
|
|
1704
2038
|
return {
|
|
1705
2039
|
register,
|
|
1706
2040
|
bootstrap,
|