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