@strapi/content-releases 0.0.0-experimental.ee4d311a5e6a131fad03cf07e4696f49fdd9c2e6 → 0.0.0-experimental.f75e3c6d67cc47c64ab37479efdbb7b43be50b78
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/dist/_chunks/App-DUmziQ17.js +1366 -0
- package/dist/_chunks/App-DUmziQ17.js.map +1 -0
- package/dist/_chunks/App-D_6Y9N2F.mjs +1344 -0
- package/dist/_chunks/App-D_6Y9N2F.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js +52 -0
- package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs +52 -0
- package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs.map +1 -0
- package/dist/_chunks/{en-MyLPoISH.mjs → en-B9Ur3VsE.mjs} +30 -7
- package/dist/_chunks/en-B9Ur3VsE.mjs.map +1 -0
- package/dist/_chunks/{en-gYDqKYFd.js → en-DtFJ5ViE.js} +30 -7
- package/dist/_chunks/en-DtFJ5ViE.js.map +1 -0
- package/dist/_chunks/{index-EIe8S-cw.mjs → index-BomF0-yY.mjs} +352 -221
- package/dist/_chunks/index-BomF0-yY.mjs.map +1 -0
- package/dist/_chunks/{index-l5iuP0Hb.js → index-C5Hc767q.js} +346 -217
- package/dist/_chunks/index-C5Hc767q.js.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/CMReleasesContainer.d.ts +22 -0
- package/dist/admin/src/components/RelativeTime.d.ts +28 -0
- package/dist/admin/src/components/ReleaseAction.d.ts +3 -0
- package/dist/admin/src/components/ReleaseActionMenu.d.ts +26 -0
- package/dist/admin/src/components/ReleaseActionOptions.d.ts +9 -0
- package/dist/admin/src/components/ReleaseListCell.d.ts +0 -0
- package/dist/admin/src/components/ReleaseModal.d.ts +16 -0
- package/dist/admin/src/constants.d.ts +58 -0
- package/dist/admin/src/index.d.ts +3 -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/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 +105 -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 +1 -0
- package/dist/server/index.js +1113 -418
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1113 -418
- 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 +12 -0
- package/dist/server/src/constants.d.ts.map +1 -0
- package/dist/server/src/content-types/index.d.ts +99 -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 +50 -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 +49 -0
- package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -0
- package/dist/server/src/controllers/index.d.ts +20 -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 +12 -0
- package/dist/server/src/controllers/release.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release-action.d.ts +8 -0
- package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release.d.ts +2 -0
- package/dist/server/src/controllers/validation/release.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 +2096 -0
- package/dist/server/src/index.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 +35 -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/services/index.d.ts +1826 -0
- package/dist/server/src/services/index.d.ts.map +1 -0
- package/dist/server/src/services/release.d.ts +66 -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/validation.d.ts +18 -0
- package/dist/server/src/services/validation.d.ts.map +1 -0
- package/dist/server/src/utils/index.d.ts +14 -0
- package/dist/server/src/utils/index.d.ts.map +1 -0
- package/dist/shared/contracts/release-actions.d.ts +131 -0
- package/dist/shared/contracts/release-actions.d.ts.map +1 -0
- package/dist/shared/contracts/releases.d.ts +182 -0
- package/dist/shared/contracts/releases.d.ts.map +1 -0
- package/dist/shared/types.d.ts +24 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/validation-schemas.d.ts +2 -0
- package/dist/shared/validation-schemas.d.ts.map +1 -0
- package/package.json +31 -35
- package/dist/_chunks/App-0yPbcoGt.js +0 -1037
- package/dist/_chunks/App-0yPbcoGt.js.map +0 -1
- package/dist/_chunks/App-BWaM2ihP.mjs +0 -1015
- package/dist/_chunks/App-BWaM2ihP.mjs.map +0 -1
- package/dist/_chunks/en-MyLPoISH.mjs.map +0 -1
- package/dist/_chunks/en-gYDqKYFd.js.map +0 -1
- package/dist/_chunks/index-EIe8S-cw.mjs.map +0 -1
- package/dist/_chunks/index-l5iuP0Hb.js.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const utils = require("@strapi/utils");
|
|
3
|
+
const isEqual = require("lodash/isEqual");
|
|
4
|
+
const lodash = require("lodash");
|
|
3
5
|
const _ = require("lodash/fp");
|
|
4
|
-
const
|
|
6
|
+
const nodeSchedule = require("node-schedule");
|
|
5
7
|
const yup = require("yup");
|
|
6
8
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
7
9
|
function _interopNamespace(e) {
|
|
@@ -22,8 +24,8 @@ function _interopNamespace(e) {
|
|
|
22
24
|
n.default = e;
|
|
23
25
|
return Object.freeze(n);
|
|
24
26
|
}
|
|
27
|
+
const isEqual__default = /* @__PURE__ */ _interopDefault(isEqual);
|
|
25
28
|
const ___default = /* @__PURE__ */ _interopDefault(_);
|
|
26
|
-
const EE__default = /* @__PURE__ */ _interopDefault(EE);
|
|
27
29
|
const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
|
|
28
30
|
const RELEASE_MODEL_UID = "plugin::content-releases.release";
|
|
29
31
|
const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
|
|
@@ -71,47 +73,255 @@ const ACTIONS = [
|
|
|
71
73
|
pluginName: "content-releases"
|
|
72
74
|
}
|
|
73
75
|
];
|
|
74
|
-
const
|
|
76
|
+
const ALLOWED_WEBHOOK_EVENTS = {
|
|
77
|
+
RELEASES_PUBLISH: "releases.publish"
|
|
78
|
+
};
|
|
79
|
+
const getService = (name, { strapi: strapi2 }) => {
|
|
75
80
|
return strapi2.plugin("content-releases").service(name);
|
|
76
81
|
};
|
|
77
|
-
const {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 }) => {
|
|
83
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
84
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
85
|
+
const entry = await strapi2.db.query(contentTypeUid).findOne({
|
|
86
|
+
where: { id: entryId },
|
|
87
|
+
populate
|
|
88
|
+
});
|
|
89
|
+
return entry;
|
|
90
|
+
};
|
|
91
|
+
const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 }) => {
|
|
92
|
+
try {
|
|
93
|
+
await strapi2.entityValidator.validateEntityCreation(
|
|
94
|
+
strapi2.getModel(contentTypeUid),
|
|
95
|
+
entry,
|
|
96
|
+
void 0,
|
|
97
|
+
// @ts-expect-error - FIXME: entity here is unnecessary
|
|
98
|
+
entry
|
|
99
|
+
);
|
|
100
|
+
return true;
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
async function deleteActionsOnDisableDraftAndPublish({
|
|
106
|
+
oldContentTypes,
|
|
107
|
+
contentTypes: contentTypes2
|
|
108
|
+
}) {
|
|
109
|
+
if (!oldContentTypes) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
for (const uid in contentTypes2) {
|
|
113
|
+
if (!oldContentTypes[uid]) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const oldContentType = oldContentTypes[uid];
|
|
117
|
+
const contentType = contentTypes2[uid];
|
|
118
|
+
if (utils.contentTypes.hasDraftAndPublish(oldContentType) && !utils.contentTypes.hasDraftAndPublish(contentType)) {
|
|
119
|
+
await strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: uid }).execute();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
124
|
+
const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
|
|
125
|
+
if (deletedContentTypes.length) {
|
|
126
|
+
await utils.async.map(deletedContentTypes, async (deletedContentTypeUID) => {
|
|
127
|
+
return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function migrateIsValidAndStatusReleases() {
|
|
132
|
+
const releasesWithoutStatus = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
133
|
+
where: {
|
|
134
|
+
status: null,
|
|
135
|
+
releasedAt: null
|
|
136
|
+
},
|
|
137
|
+
populate: {
|
|
138
|
+
actions: {
|
|
139
|
+
populate: {
|
|
140
|
+
entry: true
|
|
88
141
|
}
|
|
89
142
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
utils.async.map(releasesWithoutStatus, async (release2) => {
|
|
146
|
+
const actions = release2.actions;
|
|
147
|
+
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
148
|
+
for (const action of notValidatedActions) {
|
|
149
|
+
if (action.entry) {
|
|
150
|
+
const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
|
|
151
|
+
strapi
|
|
152
|
+
});
|
|
153
|
+
if (populatedEntry) {
|
|
154
|
+
const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
|
|
155
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
156
|
+
where: {
|
|
157
|
+
id: action.id
|
|
158
|
+
},
|
|
159
|
+
data: {
|
|
160
|
+
isEntryValid
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
167
|
+
});
|
|
168
|
+
const publishedReleases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
169
|
+
where: {
|
|
170
|
+
status: null,
|
|
171
|
+
releasedAt: {
|
|
172
|
+
$notNull: true
|
|
96
173
|
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
utils.async.map(publishedReleases, async (release2) => {
|
|
177
|
+
return strapi.db.query(RELEASE_MODEL_UID).update({
|
|
178
|
+
where: {
|
|
179
|
+
id: release2.id
|
|
180
|
+
},
|
|
181
|
+
data: {
|
|
182
|
+
status: "done"
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
188
|
+
if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
|
|
189
|
+
const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
|
|
190
|
+
(uid) => oldContentTypes[uid]?.options?.draftAndPublish
|
|
97
191
|
);
|
|
98
|
-
|
|
192
|
+
const releasesAffected = /* @__PURE__ */ new Set();
|
|
193
|
+
utils.async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
|
|
194
|
+
const oldContentType = oldContentTypes[contentTypeUID];
|
|
195
|
+
const contentType = contentTypes2[contentTypeUID];
|
|
196
|
+
if (!isEqual__default.default(oldContentType?.attributes, contentType?.attributes)) {
|
|
197
|
+
const actions = await strapi.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
198
|
+
where: {
|
|
199
|
+
contentType: contentTypeUID
|
|
200
|
+
},
|
|
201
|
+
populate: {
|
|
202
|
+
entry: true,
|
|
203
|
+
release: true
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
await utils.async.map(actions, async (action) => {
|
|
207
|
+
if (action.entry && action.release) {
|
|
208
|
+
const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
|
|
209
|
+
strapi
|
|
210
|
+
});
|
|
211
|
+
if (populatedEntry) {
|
|
212
|
+
const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
|
|
213
|
+
strapi
|
|
214
|
+
});
|
|
215
|
+
releasesAffected.add(action.release.id);
|
|
216
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
217
|
+
where: {
|
|
218
|
+
id: action.id
|
|
219
|
+
},
|
|
220
|
+
data: {
|
|
221
|
+
isEntryValid
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}).then(() => {
|
|
229
|
+
utils.async.map(releasesAffected, async (releaseId) => {
|
|
230
|
+
return getService("release", { strapi }).updateReleaseStatus(releaseId);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
236
|
+
if (!oldContentTypes) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
240
|
+
if (!i18nPlugin) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
for (const uid in contentTypes2) {
|
|
244
|
+
if (!oldContentTypes[uid]) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const oldContentType = oldContentTypes[uid];
|
|
248
|
+
const contentType = contentTypes2[uid];
|
|
249
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
250
|
+
if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
|
|
251
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
252
|
+
locale: null
|
|
253
|
+
}).where({ contentType: uid }).execute();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
258
|
+
if (!oldContentTypes) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
262
|
+
if (!i18nPlugin) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
for (const uid in contentTypes2) {
|
|
266
|
+
if (!oldContentTypes[uid]) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const oldContentType = oldContentTypes[uid];
|
|
270
|
+
const contentType = contentTypes2[uid];
|
|
271
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
272
|
+
const { getDefaultLocale } = i18nPlugin.service("locales");
|
|
273
|
+
if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
|
|
274
|
+
const defaultLocale = await getDefaultLocale();
|
|
275
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
276
|
+
locale: defaultLocale
|
|
277
|
+
}).where({ contentType: uid }).execute();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const register = async ({ strapi: strapi2 }) => {
|
|
282
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
283
|
+
await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
|
|
284
|
+
strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
|
|
285
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
286
|
+
}
|
|
287
|
+
if (strapi2.plugin("graphql")) {
|
|
288
|
+
const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
|
|
289
|
+
graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
|
|
290
|
+
graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
|
|
99
291
|
}
|
|
100
292
|
};
|
|
101
|
-
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
102
293
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
103
|
-
if (features
|
|
294
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
295
|
+
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
296
|
+
(uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
|
|
297
|
+
);
|
|
104
298
|
strapi2.db.lifecycles.subscribe({
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
299
|
+
models: contentTypesWithDraftAndPublish,
|
|
300
|
+
async afterDelete(event) {
|
|
301
|
+
try {
|
|
302
|
+
const { model, result } = event;
|
|
303
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
304
|
+
const { id } = result;
|
|
305
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
306
|
+
where: {
|
|
307
|
+
actions: {
|
|
308
|
+
target_type: model.uid,
|
|
309
|
+
target_id: id
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
314
|
+
where: {
|
|
315
|
+
target_type: model.uid,
|
|
316
|
+
target_id: id
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
for (const release2 of releases) {
|
|
320
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
113
321
|
}
|
|
114
|
-
}
|
|
322
|
+
}
|
|
323
|
+
} catch (error) {
|
|
324
|
+
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
115
325
|
}
|
|
116
326
|
},
|
|
117
327
|
/**
|
|
@@ -131,20 +341,88 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
131
341
|
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
132
342
|
*/
|
|
133
343
|
async afterDeleteMany(event) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
344
|
+
try {
|
|
345
|
+
const { model, state } = event;
|
|
346
|
+
const entriesToDelete = state.entriesToDelete;
|
|
347
|
+
if (entriesToDelete) {
|
|
348
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
349
|
+
where: {
|
|
350
|
+
actions: {
|
|
351
|
+
target_type: model.uid,
|
|
352
|
+
target_id: {
|
|
353
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
359
|
+
where: {
|
|
360
|
+
target_type: model.uid,
|
|
361
|
+
target_id: {
|
|
362
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
363
|
+
}
|
|
142
364
|
}
|
|
365
|
+
});
|
|
366
|
+
for (const release2 of releases) {
|
|
367
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
143
368
|
}
|
|
369
|
+
}
|
|
370
|
+
} catch (error) {
|
|
371
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
372
|
+
error
|
|
144
373
|
});
|
|
145
374
|
}
|
|
375
|
+
},
|
|
376
|
+
async afterUpdate(event) {
|
|
377
|
+
try {
|
|
378
|
+
const { model, result } = event;
|
|
379
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
380
|
+
const isEntryValid = await getEntryValidStatus(model.uid, result, {
|
|
381
|
+
strapi: strapi2
|
|
382
|
+
});
|
|
383
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
384
|
+
where: {
|
|
385
|
+
target_type: model.uid,
|
|
386
|
+
target_id: result.id
|
|
387
|
+
},
|
|
388
|
+
data: {
|
|
389
|
+
isEntryValid
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
393
|
+
where: {
|
|
394
|
+
actions: {
|
|
395
|
+
target_type: model.uid,
|
|
396
|
+
target_id: result.id
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
for (const release2 of releases) {
|
|
401
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} catch (error) {
|
|
405
|
+
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
406
|
+
}
|
|
146
407
|
}
|
|
147
408
|
});
|
|
409
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
410
|
+
strapi2.log.error(
|
|
411
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
412
|
+
);
|
|
413
|
+
throw err;
|
|
414
|
+
});
|
|
415
|
+
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
416
|
+
strapi2.get("webhookStore").addAllowedEvent(key, value);
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
const destroy = async ({ strapi: strapi2 }) => {
|
|
421
|
+
const scheduledJobs = getService("scheduling", {
|
|
422
|
+
strapi: strapi2
|
|
423
|
+
}).getAll();
|
|
424
|
+
for (const [, job] of scheduledJobs) {
|
|
425
|
+
job.cancel();
|
|
148
426
|
}
|
|
149
427
|
};
|
|
150
428
|
const schema$1 = {
|
|
@@ -173,6 +451,17 @@ const schema$1 = {
|
|
|
173
451
|
releasedAt: {
|
|
174
452
|
type: "datetime"
|
|
175
453
|
},
|
|
454
|
+
scheduledAt: {
|
|
455
|
+
type: "datetime"
|
|
456
|
+
},
|
|
457
|
+
timezone: {
|
|
458
|
+
type: "string"
|
|
459
|
+
},
|
|
460
|
+
status: {
|
|
461
|
+
type: "enumeration",
|
|
462
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
463
|
+
required: true
|
|
464
|
+
},
|
|
176
465
|
actions: {
|
|
177
466
|
type: "relation",
|
|
178
467
|
relation: "oneToMany",
|
|
@@ -225,6 +514,9 @@ const schema = {
|
|
|
225
514
|
relation: "manyToOne",
|
|
226
515
|
target: RELEASE_MODEL_UID,
|
|
227
516
|
inversedBy: "actions"
|
|
517
|
+
},
|
|
518
|
+
isEntryValid: {
|
|
519
|
+
type: "boolean"
|
|
228
520
|
}
|
|
229
521
|
}
|
|
230
522
|
};
|
|
@@ -235,15 +527,6 @@ const contentTypes = {
|
|
|
235
527
|
release: release$1,
|
|
236
528
|
"release-action": releaseAction$1
|
|
237
529
|
};
|
|
238
|
-
const createReleaseActionService = ({ strapi: strapi2 }) => ({
|
|
239
|
-
async deleteManyForContentType(contentTypeUid) {
|
|
240
|
-
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
241
|
-
where: {
|
|
242
|
-
target_type: contentTypeUid
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
530
|
const getGroupName = (queryValue) => {
|
|
248
531
|
switch (queryValue) {
|
|
249
532
|
case "contentType":
|
|
@@ -256,371 +539,603 @@ const getGroupName = (queryValue) => {
|
|
|
256
539
|
return "contentType.displayName";
|
|
257
540
|
}
|
|
258
541
|
};
|
|
259
|
-
const createReleaseService = ({ strapi: strapi2 }) =>
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
});
|
|
266
|
-
},
|
|
267
|
-
async findOne(id, query = {}) {
|
|
268
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
269
|
-
...query
|
|
542
|
+
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
543
|
+
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
544
|
+
strapi2.eventHub.emit(event, {
|
|
545
|
+
isPublished,
|
|
546
|
+
error,
|
|
547
|
+
release: release2
|
|
270
548
|
});
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
});
|
|
283
|
-
},
|
|
284
|
-
async findManyForContentTypeEntry(contentTypeUid, entryId, {
|
|
285
|
-
hasEntryAttached
|
|
286
|
-
} = {
|
|
287
|
-
hasEntryAttached: false
|
|
288
|
-
}) {
|
|
289
|
-
const whereActions = hasEntryAttached ? {
|
|
290
|
-
// Find all Releases where the content type entry is present
|
|
291
|
-
actions: {
|
|
292
|
-
target_type: contentTypeUid,
|
|
293
|
-
target_id: entryId
|
|
549
|
+
};
|
|
550
|
+
const publishSingleTypeAction = async (uid, actionType, entryId) => {
|
|
551
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
552
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
553
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
554
|
+
const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
|
|
555
|
+
try {
|
|
556
|
+
if (actionType === "publish") {
|
|
557
|
+
await entityManagerService.publish(entry, uid);
|
|
558
|
+
} else {
|
|
559
|
+
await entityManagerService.unpublish(entry, uid);
|
|
294
560
|
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
actions: {
|
|
301
|
-
target_type: contentTypeUid,
|
|
302
|
-
target_id: entryId
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
actions: null
|
|
308
|
-
}
|
|
309
|
-
]
|
|
310
|
-
};
|
|
311
|
-
const populateAttachedAction = hasEntryAttached ? {
|
|
312
|
-
// Filter the action to get only the content type entry
|
|
313
|
-
actions: {
|
|
314
|
-
where: {
|
|
315
|
-
target_type: contentTypeUid,
|
|
316
|
-
target_id: entryId
|
|
317
|
-
}
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
563
|
+
;
|
|
564
|
+
else {
|
|
565
|
+
throw error;
|
|
318
566
|
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
|
|
570
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
571
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
572
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
573
|
+
const entriesToPublish = await strapi2.entityService.findMany(uid, {
|
|
574
|
+
filters: {
|
|
575
|
+
id: {
|
|
576
|
+
$in: entriesToPublishIds
|
|
325
577
|
}
|
|
326
578
|
},
|
|
327
|
-
populate
|
|
328
|
-
...populateAttachedAction
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
return releases.map((release2) => {
|
|
332
|
-
if (release2.actions?.length) {
|
|
333
|
-
const [actionForEntry] = release2.actions;
|
|
334
|
-
delete release2.actions;
|
|
335
|
-
return {
|
|
336
|
-
...release2,
|
|
337
|
-
action: actionForEntry
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
return release2;
|
|
341
|
-
});
|
|
342
|
-
},
|
|
343
|
-
async update(id, releaseData, { user }) {
|
|
344
|
-
const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(releaseData);
|
|
345
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
346
|
-
if (!release2) {
|
|
347
|
-
throw new utils.errors.NotFoundError(`No release found for id ${id}`);
|
|
348
|
-
}
|
|
349
|
-
if (release2.releasedAt) {
|
|
350
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
351
|
-
}
|
|
352
|
-
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
353
|
-
/*
|
|
354
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
355
|
-
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
356
|
-
*/
|
|
357
|
-
// @ts-expect-error see above
|
|
358
|
-
data: releaseWithCreatorFields
|
|
579
|
+
populate
|
|
359
580
|
});
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
581
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
|
|
582
|
+
filters: {
|
|
583
|
+
id: {
|
|
584
|
+
$in: entriestoUnpublishIds
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
populate
|
|
365
588
|
});
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
validateUniqueEntry(releaseId, action)
|
|
369
|
-
]);
|
|
370
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
371
|
-
if (!release2) {
|
|
372
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
589
|
+
if (entriesToPublish.length > 0) {
|
|
590
|
+
await entityManagerService.publishMany(entriesToPublish, uid);
|
|
373
591
|
}
|
|
374
|
-
if (
|
|
375
|
-
|
|
592
|
+
if (entriesToUnpublish.length > 0) {
|
|
593
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, uid);
|
|
376
594
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
id: entry.id,
|
|
385
|
-
__type: entry.contentType,
|
|
386
|
-
__pivot: { field: "entry" }
|
|
387
|
-
},
|
|
388
|
-
release: releaseId
|
|
595
|
+
};
|
|
596
|
+
const getFormattedActions = async (releaseId) => {
|
|
597
|
+
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
598
|
+
where: {
|
|
599
|
+
release: {
|
|
600
|
+
id: releaseId
|
|
601
|
+
}
|
|
389
602
|
},
|
|
390
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
391
|
-
});
|
|
392
|
-
},
|
|
393
|
-
async findActions(releaseId, query) {
|
|
394
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
395
|
-
fields: ["id"]
|
|
396
|
-
});
|
|
397
|
-
if (!release2) {
|
|
398
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
399
|
-
}
|
|
400
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
401
|
-
...query,
|
|
402
603
|
populate: {
|
|
403
604
|
entry: {
|
|
404
|
-
|
|
605
|
+
fields: ["id"]
|
|
405
606
|
}
|
|
406
|
-
},
|
|
407
|
-
filters: {
|
|
408
|
-
release: releaseId
|
|
409
607
|
}
|
|
410
608
|
});
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
);
|
|
425
|
-
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
426
|
-
const formattedData = actions.map((action) => {
|
|
427
|
-
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
428
|
-
return {
|
|
429
|
-
...action,
|
|
430
|
-
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
431
|
-
contentType: {
|
|
432
|
-
displayName,
|
|
433
|
-
mainFieldValue: action.entry[mainField],
|
|
434
|
-
uid: action.contentType
|
|
609
|
+
if (actions.length === 0) {
|
|
610
|
+
throw new utils.errors.ValidationError("No entries to publish");
|
|
611
|
+
}
|
|
612
|
+
const collectionTypeActions = {};
|
|
613
|
+
const singleTypeActions = [];
|
|
614
|
+
for (const action of actions) {
|
|
615
|
+
const contentTypeUid = action.contentType;
|
|
616
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
617
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
618
|
+
collectionTypeActions[contentTypeUid] = {
|
|
619
|
+
entriesToPublishIds: [],
|
|
620
|
+
entriesToUnpublishIds: []
|
|
621
|
+
};
|
|
435
622
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
623
|
+
if (action.type === "publish") {
|
|
624
|
+
collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
|
|
625
|
+
} else {
|
|
626
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
627
|
+
}
|
|
628
|
+
} else {
|
|
629
|
+
singleTypeActions.push({
|
|
630
|
+
uid: contentTypeUid,
|
|
631
|
+
action: action.type,
|
|
632
|
+
id: action.entry.id
|
|
633
|
+
});
|
|
634
|
+
}
|
|
444
635
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
636
|
+
return { collectionTypeActions, singleTypeActions };
|
|
637
|
+
};
|
|
638
|
+
return {
|
|
639
|
+
async create(releaseData, { user }) {
|
|
640
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
|
|
641
|
+
const {
|
|
642
|
+
validatePendingReleasesLimit,
|
|
643
|
+
validateUniqueNameForPendingRelease,
|
|
644
|
+
validateScheduledAtIsLaterThanNow
|
|
645
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
646
|
+
await Promise.all([
|
|
647
|
+
validatePendingReleasesLimit(),
|
|
648
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
649
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
650
|
+
]);
|
|
651
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).create({
|
|
652
|
+
data: {
|
|
653
|
+
...releaseWithCreatorFields,
|
|
654
|
+
status: "empty"
|
|
655
|
+
}
|
|
457
656
|
});
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
return contentTypesData;
|
|
464
|
-
},
|
|
465
|
-
getContentTypeModelsFromActions(actions) {
|
|
466
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
467
|
-
if (!acc.includes(action.contentType)) {
|
|
468
|
-
acc.push(action.contentType);
|
|
657
|
+
if (releaseWithCreatorFields.scheduledAt) {
|
|
658
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
659
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
469
660
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
{}
|
|
490
|
-
);
|
|
491
|
-
return componentsMap;
|
|
492
|
-
},
|
|
493
|
-
async delete(releaseId) {
|
|
494
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
495
|
-
populate: {
|
|
496
|
-
actions: {
|
|
497
|
-
fields: ["id"]
|
|
661
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
662
|
+
return release2;
|
|
663
|
+
},
|
|
664
|
+
async findOne(id, query = {}) {
|
|
665
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query);
|
|
666
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
667
|
+
...dbQuery,
|
|
668
|
+
where: { id }
|
|
669
|
+
});
|
|
670
|
+
return release2;
|
|
671
|
+
},
|
|
672
|
+
findPage(query) {
|
|
673
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
|
|
674
|
+
return strapi2.db.query(RELEASE_MODEL_UID).findPage({
|
|
675
|
+
...dbQuery,
|
|
676
|
+
populate: {
|
|
677
|
+
actions: {
|
|
678
|
+
count: true
|
|
679
|
+
}
|
|
498
680
|
}
|
|
681
|
+
});
|
|
682
|
+
},
|
|
683
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entriesIds) {
|
|
684
|
+
let entries = entriesIds;
|
|
685
|
+
if (!Array.isArray(entriesIds)) {
|
|
686
|
+
entries = [entriesIds];
|
|
499
687
|
}
|
|
500
|
-
|
|
501
|
-
if (!release2) {
|
|
502
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
503
|
-
}
|
|
504
|
-
if (release2.releasedAt) {
|
|
505
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
506
|
-
}
|
|
507
|
-
await strapi2.db.transaction(async () => {
|
|
508
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
688
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
509
689
|
where: {
|
|
510
|
-
|
|
511
|
-
|
|
690
|
+
actions: {
|
|
691
|
+
target_type: contentTypeUid,
|
|
692
|
+
target_id: {
|
|
693
|
+
$in: entries
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
releasedAt: {
|
|
697
|
+
$null: true
|
|
512
698
|
}
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
|
516
|
-
});
|
|
517
|
-
return release2;
|
|
518
|
-
},
|
|
519
|
-
async publish(releaseId) {
|
|
520
|
-
const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
|
|
521
|
-
RELEASE_MODEL_UID,
|
|
522
|
-
releaseId,
|
|
523
|
-
{
|
|
699
|
+
},
|
|
524
700
|
populate: {
|
|
701
|
+
// Filter the action to get only the content type entry
|
|
525
702
|
actions: {
|
|
703
|
+
where: {
|
|
704
|
+
target_type: contentTypeUid,
|
|
705
|
+
target_id: {
|
|
706
|
+
$in: entries
|
|
707
|
+
}
|
|
708
|
+
},
|
|
526
709
|
populate: {
|
|
527
|
-
entry:
|
|
710
|
+
entry: {
|
|
711
|
+
select: ["id"]
|
|
712
|
+
}
|
|
528
713
|
}
|
|
529
714
|
}
|
|
530
715
|
}
|
|
716
|
+
});
|
|
717
|
+
return releases.map((release2) => {
|
|
718
|
+
if (release2.actions?.length) {
|
|
719
|
+
const actionsForEntry = release2.actions;
|
|
720
|
+
delete release2.actions;
|
|
721
|
+
return {
|
|
722
|
+
...release2,
|
|
723
|
+
actions: actionsForEntry
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
return release2;
|
|
727
|
+
});
|
|
728
|
+
},
|
|
729
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
730
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
731
|
+
where: {
|
|
732
|
+
releasedAt: {
|
|
733
|
+
$null: true
|
|
734
|
+
},
|
|
735
|
+
actions: {
|
|
736
|
+
target_type: contentTypeUid,
|
|
737
|
+
target_id: entryId
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
742
|
+
where: {
|
|
743
|
+
$or: [
|
|
744
|
+
{
|
|
745
|
+
id: {
|
|
746
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
actions: null
|
|
751
|
+
}
|
|
752
|
+
],
|
|
753
|
+
releasedAt: {
|
|
754
|
+
$null: true
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
return releases.map((release2) => {
|
|
759
|
+
if (release2.actions?.length) {
|
|
760
|
+
const [actionForEntry] = release2.actions;
|
|
761
|
+
delete release2.actions;
|
|
762
|
+
return {
|
|
763
|
+
...release2,
|
|
764
|
+
action: actionForEntry
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
return release2;
|
|
768
|
+
});
|
|
769
|
+
},
|
|
770
|
+
async update(id, releaseData, { user }) {
|
|
771
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(
|
|
772
|
+
releaseData
|
|
773
|
+
);
|
|
774
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
775
|
+
"release-validation",
|
|
776
|
+
{ strapi: strapi2 }
|
|
777
|
+
);
|
|
778
|
+
await Promise.all([
|
|
779
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
780
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
781
|
+
]);
|
|
782
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id } });
|
|
783
|
+
if (!release2) {
|
|
784
|
+
throw new utils.errors.NotFoundError(`No release found for id ${id}`);
|
|
531
785
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
786
|
+
if (release2.releasedAt) {
|
|
787
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
788
|
+
}
|
|
789
|
+
const updatedRelease = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
790
|
+
where: { id },
|
|
791
|
+
data: releaseWithCreatorFields
|
|
792
|
+
});
|
|
793
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
794
|
+
if (releaseData.scheduledAt) {
|
|
795
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
796
|
+
} else if (release2.scheduledAt) {
|
|
797
|
+
schedulingService.cancel(id);
|
|
798
|
+
}
|
|
799
|
+
this.updateReleaseStatus(id);
|
|
800
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
801
|
+
return updatedRelease;
|
|
802
|
+
},
|
|
803
|
+
async createAction(releaseId, action) {
|
|
804
|
+
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
805
|
+
strapi: strapi2
|
|
806
|
+
});
|
|
807
|
+
await Promise.all([
|
|
808
|
+
validateEntryContentType(action.entry.contentType),
|
|
809
|
+
validateUniqueEntry(releaseId, action)
|
|
810
|
+
]);
|
|
811
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
|
|
812
|
+
if (!release2) {
|
|
813
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
814
|
+
}
|
|
815
|
+
if (release2.releasedAt) {
|
|
816
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
817
|
+
}
|
|
818
|
+
const { entry, type } = action;
|
|
819
|
+
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
820
|
+
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
821
|
+
const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
|
|
822
|
+
data: {
|
|
823
|
+
type,
|
|
824
|
+
contentType: entry.contentType,
|
|
825
|
+
locale: entry.locale,
|
|
826
|
+
isEntryValid,
|
|
827
|
+
entry: {
|
|
828
|
+
id: entry.id,
|
|
829
|
+
__type: entry.contentType,
|
|
830
|
+
__pivot: { field: "entry" }
|
|
831
|
+
},
|
|
832
|
+
release: releaseId
|
|
833
|
+
},
|
|
834
|
+
populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
|
|
835
|
+
});
|
|
836
|
+
this.updateReleaseStatus(releaseId);
|
|
837
|
+
return releaseAction2;
|
|
838
|
+
},
|
|
839
|
+
async findActions(releaseId, query) {
|
|
840
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
841
|
+
where: { id: releaseId },
|
|
842
|
+
select: ["id"]
|
|
843
|
+
});
|
|
844
|
+
if (!release2) {
|
|
845
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
846
|
+
}
|
|
847
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
848
|
+
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
849
|
+
...dbQuery,
|
|
850
|
+
populate: {
|
|
851
|
+
entry: {
|
|
852
|
+
populate: "*"
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
where: {
|
|
856
|
+
release: releaseId
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
},
|
|
860
|
+
async countActions(query) {
|
|
861
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
862
|
+
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
|
|
863
|
+
},
|
|
864
|
+
async groupActions(actions, groupBy) {
|
|
865
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
866
|
+
if (!acc.includes(action.contentType)) {
|
|
867
|
+
acc.push(action.contentType);
|
|
868
|
+
}
|
|
869
|
+
return acc;
|
|
870
|
+
}, []);
|
|
871
|
+
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(contentTypeUids);
|
|
872
|
+
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
873
|
+
const formattedData = actions.map((action) => {
|
|
874
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
875
|
+
return {
|
|
876
|
+
...action,
|
|
877
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
878
|
+
contentType: {
|
|
879
|
+
displayName,
|
|
880
|
+
mainFieldValue: action.entry[mainField],
|
|
881
|
+
uid: action.contentType
|
|
882
|
+
}
|
|
549
883
|
};
|
|
884
|
+
});
|
|
885
|
+
const groupName = getGroupName(groupBy);
|
|
886
|
+
return ___default.default.groupBy(groupName)(formattedData);
|
|
887
|
+
},
|
|
888
|
+
async getLocalesDataForActions() {
|
|
889
|
+
if (!strapi2.plugin("i18n")) {
|
|
890
|
+
return {};
|
|
550
891
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
+
async getContentTypesDataForActions(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
|
+
};
|
|
555
909
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
await entityManagerService.publishMany(publish, contentTypeUid);
|
|
910
|
+
return contentTypesData;
|
|
911
|
+
},
|
|
912
|
+
getContentTypeModelsFromActions(actions) {
|
|
913
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
914
|
+
if (!acc.includes(action.contentType)) {
|
|
915
|
+
acc.push(action.contentType);
|
|
563
916
|
}
|
|
564
|
-
|
|
565
|
-
|
|
917
|
+
return acc;
|
|
918
|
+
}, []);
|
|
919
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
920
|
+
(acc, contentTypeUid) => {
|
|
921
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
922
|
+
return acc;
|
|
923
|
+
},
|
|
924
|
+
{}
|
|
925
|
+
);
|
|
926
|
+
return contentTypeModelsMap;
|
|
927
|
+
},
|
|
928
|
+
async getAllComponents() {
|
|
929
|
+
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
930
|
+
const components = await contentManagerComponentsService.findAllComponents();
|
|
931
|
+
const componentsMap = components.reduce(
|
|
932
|
+
(acc, component) => {
|
|
933
|
+
acc[component.uid] = component;
|
|
934
|
+
return acc;
|
|
935
|
+
},
|
|
936
|
+
{}
|
|
937
|
+
);
|
|
938
|
+
return componentsMap;
|
|
939
|
+
},
|
|
940
|
+
async delete(releaseId) {
|
|
941
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
942
|
+
where: { id: releaseId },
|
|
943
|
+
populate: {
|
|
944
|
+
actions: {
|
|
945
|
+
select: ["id"]
|
|
946
|
+
}
|
|
566
947
|
}
|
|
948
|
+
});
|
|
949
|
+
if (!release2) {
|
|
950
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
567
951
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
data: {
|
|
571
|
-
/*
|
|
572
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
|
|
573
|
-
*/
|
|
574
|
-
// @ts-expect-error see above
|
|
575
|
-
releasedAt: /* @__PURE__ */ new Date()
|
|
952
|
+
if (release2.releasedAt) {
|
|
953
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
576
954
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
id: actionId,
|
|
584
|
-
release: {
|
|
585
|
-
id: releaseId,
|
|
586
|
-
releasedAt: {
|
|
587
|
-
$null: true
|
|
955
|
+
await strapi2.db.transaction(async () => {
|
|
956
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
957
|
+
where: {
|
|
958
|
+
id: {
|
|
959
|
+
$in: release2.actions.map((action) => action.id)
|
|
960
|
+
}
|
|
588
961
|
}
|
|
962
|
+
});
|
|
963
|
+
await strapi2.db.query(RELEASE_MODEL_UID).delete({
|
|
964
|
+
where: {
|
|
965
|
+
id: releaseId
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
});
|
|
969
|
+
if (release2.scheduledAt) {
|
|
970
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
971
|
+
await schedulingService.cancel(release2.id);
|
|
972
|
+
}
|
|
973
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
974
|
+
return release2;
|
|
975
|
+
},
|
|
976
|
+
async publish(releaseId) {
|
|
977
|
+
const {
|
|
978
|
+
release: release2,
|
|
979
|
+
error
|
|
980
|
+
} = await strapi2.db.transaction(async ({ trx }) => {
|
|
981
|
+
const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
|
|
982
|
+
if (!lockedRelease) {
|
|
983
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
589
984
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
985
|
+
if (lockedRelease.releasedAt) {
|
|
986
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
987
|
+
}
|
|
988
|
+
if (lockedRelease.status === "failed") {
|
|
989
|
+
throw new utils.errors.ValidationError("Release failed to publish");
|
|
990
|
+
}
|
|
991
|
+
try {
|
|
992
|
+
strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
|
|
993
|
+
const { collectionTypeActions, singleTypeActions } = await getFormattedActions(releaseId);
|
|
994
|
+
await strapi2.db.transaction(async () => {
|
|
995
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
996
|
+
await publishSingleTypeAction(uid, action, id);
|
|
997
|
+
}
|
|
998
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
999
|
+
const uid = contentTypeUid;
|
|
1000
|
+
await publishCollectionTypeAction(
|
|
1001
|
+
uid,
|
|
1002
|
+
collectionTypeActions[uid].entriesToPublishIds,
|
|
1003
|
+
collectionTypeActions[uid].entriesToUnpublishIds
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1008
|
+
where: {
|
|
1009
|
+
id: releaseId
|
|
1010
|
+
},
|
|
1011
|
+
data: {
|
|
1012
|
+
status: "done",
|
|
1013
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
1017
|
+
isPublished: true,
|
|
1018
|
+
release: release22
|
|
1019
|
+
});
|
|
1020
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
1021
|
+
return { release: release22, error: null };
|
|
1022
|
+
} catch (error2) {
|
|
1023
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
1024
|
+
isPublished: false,
|
|
1025
|
+
error: error2
|
|
1026
|
+
});
|
|
1027
|
+
await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
|
|
1028
|
+
status: "failed"
|
|
1029
|
+
}).transacting(trx).execute();
|
|
1030
|
+
return {
|
|
1031
|
+
release: null,
|
|
1032
|
+
error: error2
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
if (error instanceof Error) {
|
|
1037
|
+
throw error;
|
|
1038
|
+
}
|
|
1039
|
+
return release2;
|
|
1040
|
+
},
|
|
1041
|
+
async updateAction(actionId, releaseId, update) {
|
|
1042
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1043
|
+
where: {
|
|
1044
|
+
id: actionId,
|
|
1045
|
+
release: {
|
|
1046
|
+
id: releaseId,
|
|
1047
|
+
releasedAt: {
|
|
1048
|
+
$null: true
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
},
|
|
1052
|
+
data: update
|
|
1053
|
+
});
|
|
1054
|
+
if (!updatedAction) {
|
|
1055
|
+
throw new utils.errors.NotFoundError(
|
|
1056
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
return updatedAction;
|
|
1060
|
+
},
|
|
1061
|
+
async deleteAction(actionId, releaseId) {
|
|
1062
|
+
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
1063
|
+
where: {
|
|
1064
|
+
id: actionId,
|
|
1065
|
+
release: {
|
|
1066
|
+
id: releaseId,
|
|
1067
|
+
releasedAt: {
|
|
1068
|
+
$null: true
|
|
1069
|
+
}
|
|
608
1070
|
}
|
|
609
1071
|
}
|
|
1072
|
+
});
|
|
1073
|
+
if (!deletedAction) {
|
|
1074
|
+
throw new utils.errors.NotFoundError(
|
|
1075
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1076
|
+
);
|
|
610
1077
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
1078
|
+
this.updateReleaseStatus(releaseId);
|
|
1079
|
+
return deletedAction;
|
|
1080
|
+
},
|
|
1081
|
+
async updateReleaseStatus(releaseId) {
|
|
1082
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
1083
|
+
this.countActions({
|
|
1084
|
+
filters: {
|
|
1085
|
+
release: releaseId
|
|
1086
|
+
}
|
|
1087
|
+
}),
|
|
1088
|
+
this.countActions({
|
|
1089
|
+
filters: {
|
|
1090
|
+
release: releaseId,
|
|
1091
|
+
isEntryValid: false
|
|
1092
|
+
}
|
|
1093
|
+
})
|
|
1094
|
+
]);
|
|
1095
|
+
if (totalActions > 0) {
|
|
1096
|
+
if (invalidActions > 0) {
|
|
1097
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1098
|
+
where: {
|
|
1099
|
+
id: releaseId
|
|
1100
|
+
},
|
|
1101
|
+
data: {
|
|
1102
|
+
status: "blocked"
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1107
|
+
where: {
|
|
1108
|
+
id: releaseId
|
|
1109
|
+
},
|
|
1110
|
+
data: {
|
|
1111
|
+
status: "ready"
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1116
|
+
where: {
|
|
1117
|
+
id: releaseId
|
|
1118
|
+
},
|
|
1119
|
+
data: {
|
|
1120
|
+
status: "empty"
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
616
1123
|
}
|
|
617
|
-
|
|
1124
|
+
};
|
|
1125
|
+
};
|
|
1126
|
+
class AlreadyOnReleaseError extends utils.errors.ApplicationError {
|
|
1127
|
+
constructor(message) {
|
|
1128
|
+
super(message);
|
|
1129
|
+
this.name = "AlreadyOnReleaseError";
|
|
618
1130
|
}
|
|
619
|
-
}
|
|
1131
|
+
}
|
|
620
1132
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
621
1133
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
622
|
-
const release2 = await strapi2.
|
|
623
|
-
|
|
1134
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
1135
|
+
where: {
|
|
1136
|
+
id: releaseId
|
|
1137
|
+
},
|
|
1138
|
+
populate: { actions: { populate: { entry: { select: ["id"] } } } }
|
|
624
1139
|
});
|
|
625
1140
|
if (!release2) {
|
|
626
1141
|
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
@@ -629,7 +1144,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
629
1144
|
(action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
|
|
630
1145
|
);
|
|
631
1146
|
if (isEntryInRelease) {
|
|
632
|
-
throw new
|
|
1147
|
+
throw new AlreadyOnReleaseError(
|
|
633
1148
|
`Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
|
|
634
1149
|
);
|
|
635
1150
|
}
|
|
@@ -646,10 +1161,8 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
646
1161
|
}
|
|
647
1162
|
},
|
|
648
1163
|
async validatePendingReleasesLimit() {
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
EE__default.default.features.get("cms-content-releases")?.options?.maximumReleases || 3
|
|
652
|
-
);
|
|
1164
|
+
const featureCfg = strapi2.ee.features.get("cms-content-releases");
|
|
1165
|
+
const maximumPendingReleases = typeof featureCfg === "object" && featureCfg?.options?.maximumReleases || 3;
|
|
653
1166
|
const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
|
|
654
1167
|
filters: {
|
|
655
1168
|
releasedAt: {
|
|
@@ -660,39 +1173,109 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
660
1173
|
if (pendingReleasesCount >= maximumPendingReleases) {
|
|
661
1174
|
throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
|
|
662
1175
|
}
|
|
1176
|
+
},
|
|
1177
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
1178
|
+
const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1179
|
+
where: {
|
|
1180
|
+
releasedAt: {
|
|
1181
|
+
$null: true
|
|
1182
|
+
},
|
|
1183
|
+
name,
|
|
1184
|
+
...id && { id: { $ne: id } }
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
const isNameUnique = pendingReleases.length === 0;
|
|
1188
|
+
if (!isNameUnique) {
|
|
1189
|
+
throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
|
|
1190
|
+
}
|
|
1191
|
+
},
|
|
1192
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
1193
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
1194
|
+
throw new utils.errors.ValidationError("Scheduled at must be later than now");
|
|
1195
|
+
}
|
|
663
1196
|
}
|
|
664
1197
|
});
|
|
665
|
-
const
|
|
666
|
-
const
|
|
667
|
-
destroyListenerCallbacks: []
|
|
668
|
-
};
|
|
1198
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
1199
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
669
1200
|
return {
|
|
670
|
-
|
|
671
|
-
|
|
1201
|
+
async set(releaseId, scheduleDate) {
|
|
1202
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
1203
|
+
if (!release2) {
|
|
1204
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1205
|
+
}
|
|
1206
|
+
const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
|
|
1207
|
+
try {
|
|
1208
|
+
await getService("release", { strapi: strapi2 }).publish(releaseId);
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
}
|
|
1211
|
+
this.cancel(releaseId);
|
|
1212
|
+
});
|
|
1213
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1214
|
+
this.cancel(releaseId);
|
|
1215
|
+
}
|
|
1216
|
+
scheduledJobs.set(releaseId, job);
|
|
1217
|
+
return scheduledJobs;
|
|
672
1218
|
},
|
|
673
|
-
|
|
674
|
-
if (
|
|
675
|
-
|
|
1219
|
+
cancel(releaseId) {
|
|
1220
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1221
|
+
scheduledJobs.get(releaseId).cancel();
|
|
1222
|
+
scheduledJobs.delete(releaseId);
|
|
676
1223
|
}
|
|
677
|
-
|
|
678
|
-
|
|
1224
|
+
return scheduledJobs;
|
|
1225
|
+
},
|
|
1226
|
+
getAll() {
|
|
1227
|
+
return scheduledJobs;
|
|
1228
|
+
},
|
|
1229
|
+
/**
|
|
1230
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
1231
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
1232
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
1233
|
+
*/
|
|
1234
|
+
async syncFromDatabase() {
|
|
1235
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1236
|
+
where: {
|
|
1237
|
+
scheduledAt: {
|
|
1238
|
+
$gte: /* @__PURE__ */ new Date()
|
|
1239
|
+
},
|
|
1240
|
+
releasedAt: null
|
|
1241
|
+
}
|
|
679
1242
|
});
|
|
1243
|
+
for (const release2 of releases) {
|
|
1244
|
+
this.set(release2.id, release2.scheduledAt);
|
|
1245
|
+
}
|
|
1246
|
+
return scheduledJobs;
|
|
680
1247
|
}
|
|
681
1248
|
};
|
|
682
1249
|
};
|
|
683
1250
|
const services = {
|
|
684
1251
|
release: createReleaseService,
|
|
685
|
-
"release-action": createReleaseActionService,
|
|
686
1252
|
"release-validation": createReleaseValidationService,
|
|
687
|
-
|
|
1253
|
+
scheduling: createSchedulingService
|
|
688
1254
|
};
|
|
689
1255
|
const RELEASE_SCHEMA = yup__namespace.object().shape({
|
|
690
|
-
name: yup__namespace.string().trim().required()
|
|
1256
|
+
name: yup__namespace.string().trim().required(),
|
|
1257
|
+
scheduledAt: yup__namespace.string().nullable(),
|
|
1258
|
+
isScheduled: yup__namespace.boolean().optional(),
|
|
1259
|
+
time: yup__namespace.string().when("isScheduled", {
|
|
1260
|
+
is: true,
|
|
1261
|
+
then: yup__namespace.string().trim().required(),
|
|
1262
|
+
otherwise: yup__namespace.string().nullable()
|
|
1263
|
+
}),
|
|
1264
|
+
timezone: yup__namespace.string().when("isScheduled", {
|
|
1265
|
+
is: true,
|
|
1266
|
+
then: yup__namespace.string().required().nullable(),
|
|
1267
|
+
otherwise: yup__namespace.string().nullable()
|
|
1268
|
+
}),
|
|
1269
|
+
date: yup__namespace.string().when("isScheduled", {
|
|
1270
|
+
is: true,
|
|
1271
|
+
then: yup__namespace.string().required().nullable(),
|
|
1272
|
+
otherwise: yup__namespace.string().nullable()
|
|
1273
|
+
})
|
|
691
1274
|
}).required().noUnknown();
|
|
692
1275
|
const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
|
|
693
1276
|
const releaseController = {
|
|
694
1277
|
async findMany(ctx) {
|
|
695
|
-
const permissionsManager = strapi.admin
|
|
1278
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
696
1279
|
ability: ctx.state.userAbility,
|
|
697
1280
|
model: RELEASE_MODEL_UID
|
|
698
1281
|
});
|
|
@@ -704,9 +1287,7 @@ const releaseController = {
|
|
|
704
1287
|
const contentTypeUid = query.contentTypeUid;
|
|
705
1288
|
const entryId = query.entryId;
|
|
706
1289
|
const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
|
|
707
|
-
const data = await releaseService.
|
|
708
|
-
hasEntryAttached
|
|
709
|
-
});
|
|
1290
|
+
const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
|
|
710
1291
|
ctx.body = { data };
|
|
711
1292
|
} else {
|
|
712
1293
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
@@ -722,26 +1303,30 @@ const releaseController = {
|
|
|
722
1303
|
}
|
|
723
1304
|
};
|
|
724
1305
|
});
|
|
725
|
-
|
|
1306
|
+
const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
|
|
1307
|
+
where: {
|
|
1308
|
+
releasedAt: null
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
726
1312
|
}
|
|
727
1313
|
},
|
|
728
1314
|
async findOne(ctx) {
|
|
729
1315
|
const id = ctx.params.id;
|
|
730
1316
|
const releaseService = getService("release", { strapi });
|
|
731
1317
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
});
|
|
736
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
1318
|
+
if (!release2) {
|
|
1319
|
+
throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
|
|
1320
|
+
}
|
|
737
1321
|
const count = await releaseService.countActions({
|
|
738
1322
|
filters: {
|
|
739
1323
|
release: id
|
|
740
1324
|
}
|
|
741
1325
|
});
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
1326
|
+
const sanitizedRelease = {
|
|
1327
|
+
...release2,
|
|
1328
|
+
createdBy: release2.createdBy ? strapi.service("admin::user").sanitizeUser(release2.createdBy) : null
|
|
1329
|
+
};
|
|
745
1330
|
const data = {
|
|
746
1331
|
...sanitizedRelease,
|
|
747
1332
|
actions: {
|
|
@@ -752,19 +1337,48 @@ const releaseController = {
|
|
|
752
1337
|
};
|
|
753
1338
|
ctx.body = { data };
|
|
754
1339
|
},
|
|
1340
|
+
async mapEntriesToReleases(ctx) {
|
|
1341
|
+
const { contentTypeUid, entriesIds } = ctx.query;
|
|
1342
|
+
if (!contentTypeUid || !entriesIds) {
|
|
1343
|
+
throw new utils.errors.ValidationError("Missing required query parameters");
|
|
1344
|
+
}
|
|
1345
|
+
const releaseService = getService("release", { strapi });
|
|
1346
|
+
const releasesWithActions = await releaseService.findManyWithContentTypeEntryAttached(
|
|
1347
|
+
contentTypeUid,
|
|
1348
|
+
entriesIds
|
|
1349
|
+
);
|
|
1350
|
+
const mappedEntriesInReleases = releasesWithActions.reduce(
|
|
1351
|
+
// TODO: Fix for v5 removed mappedEntriedToRelease
|
|
1352
|
+
(acc, release2) => {
|
|
1353
|
+
release2.actions.forEach((action) => {
|
|
1354
|
+
if (!acc[action.entry.id]) {
|
|
1355
|
+
acc[action.entry.id] = [{ id: release2.id, name: release2.name }];
|
|
1356
|
+
} else {
|
|
1357
|
+
acc[action.entry.id].push({ id: release2.id, name: release2.name });
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1360
|
+
return acc;
|
|
1361
|
+
},
|
|
1362
|
+
// TODO: Fix for v5 removed mappedEntriedToRelease
|
|
1363
|
+
{}
|
|
1364
|
+
);
|
|
1365
|
+
ctx.body = {
|
|
1366
|
+
data: mappedEntriesInReleases
|
|
1367
|
+
};
|
|
1368
|
+
},
|
|
755
1369
|
async create(ctx) {
|
|
756
1370
|
const user = ctx.state.user;
|
|
757
1371
|
const releaseArgs = ctx.request.body;
|
|
758
1372
|
await validateRelease(releaseArgs);
|
|
759
1373
|
const releaseService = getService("release", { strapi });
|
|
760
1374
|
const release2 = await releaseService.create(releaseArgs, { user });
|
|
761
|
-
const permissionsManager = strapi.admin
|
|
1375
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
762
1376
|
ability: ctx.state.userAbility,
|
|
763
1377
|
model: RELEASE_MODEL_UID
|
|
764
1378
|
});
|
|
765
|
-
ctx.
|
|
1379
|
+
ctx.created({
|
|
766
1380
|
data: await permissionsManager.sanitizeOutput(release2)
|
|
767
|
-
};
|
|
1381
|
+
});
|
|
768
1382
|
},
|
|
769
1383
|
async update(ctx) {
|
|
770
1384
|
const user = ctx.state.user;
|
|
@@ -773,7 +1387,7 @@ const releaseController = {
|
|
|
773
1387
|
await validateRelease(releaseArgs);
|
|
774
1388
|
const releaseService = getService("release", { strapi });
|
|
775
1389
|
const release2 = await releaseService.update(id, releaseArgs, { user });
|
|
776
|
-
const permissionsManager = strapi.admin
|
|
1390
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
777
1391
|
ability: ctx.state.userAbility,
|
|
778
1392
|
model: RELEASE_MODEL_UID
|
|
779
1393
|
});
|
|
@@ -794,8 +1408,27 @@ const releaseController = {
|
|
|
794
1408
|
const id = ctx.params.id;
|
|
795
1409
|
const releaseService = getService("release", { strapi });
|
|
796
1410
|
const release2 = await releaseService.publish(id, { user });
|
|
1411
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1412
|
+
releaseService.countActions({
|
|
1413
|
+
filters: {
|
|
1414
|
+
release: id,
|
|
1415
|
+
type: "publish"
|
|
1416
|
+
}
|
|
1417
|
+
}),
|
|
1418
|
+
releaseService.countActions({
|
|
1419
|
+
filters: {
|
|
1420
|
+
release: id,
|
|
1421
|
+
type: "unpublish"
|
|
1422
|
+
}
|
|
1423
|
+
})
|
|
1424
|
+
]);
|
|
797
1425
|
ctx.body = {
|
|
798
|
-
data: release2
|
|
1426
|
+
data: release2,
|
|
1427
|
+
meta: {
|
|
1428
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1429
|
+
totalPublishedEntries: countPublishActions,
|
|
1430
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1431
|
+
}
|
|
799
1432
|
};
|
|
800
1433
|
}
|
|
801
1434
|
};
|
|
@@ -818,13 +1451,45 @@ const releaseActionController = {
|
|
|
818
1451
|
await validateReleaseAction(releaseActionArgs);
|
|
819
1452
|
const releaseService = getService("release", { strapi });
|
|
820
1453
|
const releaseAction2 = await releaseService.createAction(releaseId, releaseActionArgs);
|
|
821
|
-
ctx.
|
|
1454
|
+
ctx.created({
|
|
822
1455
|
data: releaseAction2
|
|
823
|
-
};
|
|
1456
|
+
});
|
|
1457
|
+
},
|
|
1458
|
+
async createMany(ctx) {
|
|
1459
|
+
const releaseId = ctx.params.releaseId;
|
|
1460
|
+
const releaseActionsArgs = ctx.request.body;
|
|
1461
|
+
await Promise.all(
|
|
1462
|
+
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
|
1463
|
+
);
|
|
1464
|
+
const releaseService = getService("release", { strapi });
|
|
1465
|
+
const releaseActions = await strapi.db.transaction(async () => {
|
|
1466
|
+
const releaseActions2 = await Promise.all(
|
|
1467
|
+
releaseActionsArgs.map(async (releaseActionArgs) => {
|
|
1468
|
+
try {
|
|
1469
|
+
const action = await releaseService.createAction(releaseId, releaseActionArgs);
|
|
1470
|
+
return action;
|
|
1471
|
+
} catch (error) {
|
|
1472
|
+
if (error instanceof AlreadyOnReleaseError) {
|
|
1473
|
+
return null;
|
|
1474
|
+
}
|
|
1475
|
+
throw error;
|
|
1476
|
+
}
|
|
1477
|
+
})
|
|
1478
|
+
);
|
|
1479
|
+
return releaseActions2;
|
|
1480
|
+
});
|
|
1481
|
+
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
|
1482
|
+
ctx.created({
|
|
1483
|
+
data: newReleaseActions,
|
|
1484
|
+
meta: {
|
|
1485
|
+
entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
|
|
1486
|
+
totalEntries: releaseActions.length
|
|
1487
|
+
}
|
|
1488
|
+
});
|
|
824
1489
|
},
|
|
825
1490
|
async findMany(ctx) {
|
|
826
1491
|
const releaseId = ctx.params.releaseId;
|
|
827
|
-
const permissionsManager = strapi.admin
|
|
1492
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
828
1493
|
ability: ctx.state.userAbility,
|
|
829
1494
|
model: RELEASE_ACTION_MODEL_UID
|
|
830
1495
|
});
|
|
@@ -838,14 +1503,14 @@ const releaseActionController = {
|
|
|
838
1503
|
if (acc[action.contentType]) {
|
|
839
1504
|
return acc;
|
|
840
1505
|
}
|
|
841
|
-
const contentTypePermissionsManager = strapi.admin
|
|
1506
|
+
const contentTypePermissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
842
1507
|
ability: ctx.state.userAbility,
|
|
843
1508
|
model: action.contentType
|
|
844
1509
|
});
|
|
845
1510
|
acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
|
|
846
1511
|
return acc;
|
|
847
1512
|
}, {});
|
|
848
|
-
const sanitizedResults = await utils.
|
|
1513
|
+
const sanitizedResults = await utils.async.map(results, async (action) => ({
|
|
849
1514
|
...action,
|
|
850
1515
|
entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
|
|
851
1516
|
}));
|
|
@@ -890,6 +1555,22 @@ const controllers = { release: releaseController, "release-action": releaseActio
|
|
|
890
1555
|
const release = {
|
|
891
1556
|
type: "admin",
|
|
892
1557
|
routes: [
|
|
1558
|
+
{
|
|
1559
|
+
method: "GET",
|
|
1560
|
+
path: "/mapEntriesToReleases",
|
|
1561
|
+
handler: "release.mapEntriesToReleases",
|
|
1562
|
+
config: {
|
|
1563
|
+
policies: [
|
|
1564
|
+
"admin::isAuthenticatedAdmin",
|
|
1565
|
+
{
|
|
1566
|
+
name: "admin::hasPermissions",
|
|
1567
|
+
config: {
|
|
1568
|
+
actions: ["plugin::content-releases.read"]
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
]
|
|
1572
|
+
}
|
|
1573
|
+
},
|
|
893
1574
|
{
|
|
894
1575
|
method: "POST",
|
|
895
1576
|
path: "/",
|
|
@@ -1007,6 +1688,22 @@ const releaseAction = {
|
|
|
1007
1688
|
]
|
|
1008
1689
|
}
|
|
1009
1690
|
},
|
|
1691
|
+
{
|
|
1692
|
+
method: "POST",
|
|
1693
|
+
path: "/:releaseId/actions/bulk",
|
|
1694
|
+
handler: "release-action.createMany",
|
|
1695
|
+
config: {
|
|
1696
|
+
policies: [
|
|
1697
|
+
"admin::isAuthenticatedAdmin",
|
|
1698
|
+
{
|
|
1699
|
+
name: "admin::hasPermissions",
|
|
1700
|
+
config: {
|
|
1701
|
+
actions: ["plugin::content-releases.create-action"]
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
]
|
|
1705
|
+
}
|
|
1706
|
+
},
|
|
1010
1707
|
{
|
|
1011
1708
|
method: "GET",
|
|
1012
1709
|
path: "/:releaseId/actions",
|
|
@@ -1061,24 +1758,22 @@ const routes = {
|
|
|
1061
1758
|
release,
|
|
1062
1759
|
"release-action": releaseAction
|
|
1063
1760
|
};
|
|
1064
|
-
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
1065
1761
|
const getPlugin = () => {
|
|
1066
|
-
if (features.isEnabled("cms-content-releases")) {
|
|
1762
|
+
if (strapi.ee.features.isEnabled("cms-content-releases")) {
|
|
1067
1763
|
return {
|
|
1068
1764
|
register,
|
|
1069
1765
|
bootstrap,
|
|
1766
|
+
destroy,
|
|
1070
1767
|
contentTypes,
|
|
1071
1768
|
services,
|
|
1072
1769
|
controllers,
|
|
1073
|
-
routes
|
|
1074
|
-
destroy() {
|
|
1075
|
-
if (features.isEnabled("cms-content-releases")) {
|
|
1076
|
-
getService("event-manager").destroyAllListeners();
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1770
|
+
routes
|
|
1079
1771
|
};
|
|
1080
1772
|
}
|
|
1081
1773
|
return {
|
|
1774
|
+
// Always return register, it handles its own feature check
|
|
1775
|
+
register,
|
|
1776
|
+
// Always return contentTypes to avoid losing data when the feature is disabled
|
|
1082
1777
|
contentTypes
|
|
1083
1778
|
};
|
|
1084
1779
|
};
|