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