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