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