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