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