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