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