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