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