@strapi/content-releases 0.0.0-experimental.cae3a5a17d131a6f59673b62d01cfac869ea9cc2 → 0.0.0-experimental.d362bf200f5f9359a4bbd4a549603de5ee1f04ca
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/dist/_chunks/App-1LckaIGY.js +1352 -0
- package/dist/_chunks/App-1LckaIGY.js.map +1 -0
- package/dist/_chunks/App-X01LBg5V.mjs +1329 -0
- package/dist/_chunks/App-X01LBg5V.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs +51 -0
- package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js +51 -0
- package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +1 -0
- package/dist/_chunks/{en-SOqjCdyh.mjs → en-RdapH-9X.mjs} +18 -7
- package/dist/_chunks/en-RdapH-9X.mjs.map +1 -0
- package/dist/_chunks/{en-2DuPv5k0.js → en-faJDuv3q.js} +18 -7
- package/dist/_chunks/en-faJDuv3q.js.map +1 -0
- package/dist/_chunks/{index-bsuc8ZwZ.mjs → index-OD9AlD-6.mjs} +233 -107
- package/dist/_chunks/index-OD9AlD-6.mjs.map +1 -0
- package/dist/_chunks/{index-_lT-gI3M.js → index-cYWov2wa.js} +226 -100
- package/dist/_chunks/index-cYWov2wa.js.map +1 -0
- package/dist/admin/index.js +1 -15
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +1 -15
- package/dist/admin/index.mjs.map +1 -1
- package/dist/admin/src/components/CMReleasesContainer.d.ts +1 -0
- package/dist/admin/src/components/RelativeTime.d.ts +28 -0
- package/dist/admin/src/components/ReleaseActionMenu.d.ts +26 -0
- package/dist/admin/src/components/ReleaseActionOptions.d.ts +9 -0
- package/dist/admin/src/components/ReleaseModal.d.ts +16 -0
- package/dist/admin/src/constants.d.ts +58 -0
- package/dist/admin/src/index.d.ts +3 -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/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/axios.d.ts +29 -0
- package/dist/admin/src/services/release.d.ts +369 -0
- package/dist/admin/src/store/hooks.d.ts +7 -0
- package/dist/admin/src/utils/prefixPluginTranslations.d.ts +3 -0
- package/dist/admin/src/utils/time.d.ts +1 -0
- package/dist/server/index.js +1014 -417
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1014 -417
- 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 +12 -0
- package/dist/server/src/constants.d.ts.map +1 -0
- package/dist/server/src/content-types/index.d.ts +99 -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 +50 -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 +49 -0
- package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -0
- package/dist/server/src/controllers/index.d.ts +19 -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 +11 -0
- package/dist/server/src/controllers/release.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release-action.d.ts +8 -0
- package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release.d.ts +2 -0
- package/dist/server/src/controllers/validation/release.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 +2095 -0
- package/dist/server/src/index.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 +35 -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/services/index.d.ts +1826 -0
- package/dist/server/src/services/index.d.ts.map +1 -0
- package/dist/server/src/services/release.d.ts +66 -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/validation.d.ts +18 -0
- package/dist/server/src/services/validation.d.ts.map +1 -0
- package/dist/server/src/utils/index.d.ts +14 -0
- package/dist/server/src/utils/index.d.ts.map +1 -0
- package/dist/shared/contracts/release-actions.d.ts +131 -0
- package/dist/shared/contracts/release-actions.d.ts.map +1 -0
- package/dist/shared/contracts/releases.d.ts +166 -0
- package/dist/shared/contracts/releases.d.ts.map +1 -0
- package/dist/shared/types.d.ts +24 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/validation-schemas.d.ts +2 -0
- package/dist/shared/validation-schemas.d.ts.map +1 -0
- package/package.json +25 -29
- package/dist/_chunks/App-_Jj3tWts.mjs +0 -1053
- package/dist/_chunks/App-_Jj3tWts.mjs.map +0 -1
- package/dist/_chunks/App-iqqoPnBO.js +0 -1075
- package/dist/_chunks/App-iqqoPnBO.js.map +0 -1
- package/dist/_chunks/en-2DuPv5k0.js.map +0 -1
- package/dist/_chunks/en-SOqjCdyh.mjs.map +0 -1
- package/dist/_chunks/index-_lT-gI3M.js.map +0 -1
- package/dist/_chunks/index-bsuc8ZwZ.mjs.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
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
|
|
6
|
+
const nodeSchedule = require("node-schedule");
|
|
6
7
|
const yup = require("yup");
|
|
7
8
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
8
9
|
function _interopNamespace(e) {
|
|
@@ -23,8 +24,8 @@ function _interopNamespace(e) {
|
|
|
23
24
|
n.default = e;
|
|
24
25
|
return Object.freeze(n);
|
|
25
26
|
}
|
|
27
|
+
const isEqual__default = /* @__PURE__ */ _interopDefault(isEqual);
|
|
26
28
|
const ___default = /* @__PURE__ */ _interopDefault(_);
|
|
27
|
-
const EE__default = /* @__PURE__ */ _interopDefault(EE);
|
|
28
29
|
const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
|
|
29
30
|
const RELEASE_MODEL_UID = "plugin::content-releases.release";
|
|
30
31
|
const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
|
|
@@ -72,6 +73,35 @@ const ACTIONS = [
|
|
|
72
73
|
pluginName: "content-releases"
|
|
73
74
|
}
|
|
74
75
|
];
|
|
76
|
+
const ALLOWED_WEBHOOK_EVENTS = {
|
|
77
|
+
RELEASES_PUBLISH: "releases.publish"
|
|
78
|
+
};
|
|
79
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
80
|
+
return strapi2.plugin("content-releases").service(name);
|
|
81
|
+
};
|
|
82
|
+
const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
83
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
84
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
85
|
+
const entry = await strapi2.db.query(contentTypeUid).findOne({
|
|
86
|
+
where: { id: entryId },
|
|
87
|
+
populate
|
|
88
|
+
});
|
|
89
|
+
return entry;
|
|
90
|
+
};
|
|
91
|
+
const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
92
|
+
try {
|
|
93
|
+
await strapi2.entityValidator.validateEntityCreation(
|
|
94
|
+
strapi2.getModel(contentTypeUid),
|
|
95
|
+
entry,
|
|
96
|
+
void 0,
|
|
97
|
+
// @ts-expect-error - FIXME: entity here is unnecessary
|
|
98
|
+
entry
|
|
99
|
+
);
|
|
100
|
+
return true;
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
75
105
|
async function deleteActionsOnDisableDraftAndPublish({
|
|
76
106
|
oldContentTypes,
|
|
77
107
|
contentTypes: contentTypes2
|
|
@@ -93,33 +123,199 @@ async function deleteActionsOnDisableDraftAndPublish({
|
|
|
93
123
|
async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
94
124
|
const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
|
|
95
125
|
if (deletedContentTypes.length) {
|
|
96
|
-
await utils.
|
|
126
|
+
await utils.async.map(deletedContentTypes, async (deletedContentTypeUID) => {
|
|
97
127
|
return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
|
|
98
128
|
});
|
|
99
129
|
}
|
|
100
130
|
}
|
|
101
|
-
|
|
131
|
+
async function migrateIsValidAndStatusReleases() {
|
|
132
|
+
const releasesWithoutStatus = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
133
|
+
where: {
|
|
134
|
+
status: null,
|
|
135
|
+
releasedAt: null
|
|
136
|
+
},
|
|
137
|
+
populate: {
|
|
138
|
+
actions: {
|
|
139
|
+
populate: {
|
|
140
|
+
entry: true
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
utils.async.map(releasesWithoutStatus, async (release2) => {
|
|
146
|
+
const actions = release2.actions;
|
|
147
|
+
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
148
|
+
for (const action of notValidatedActions) {
|
|
149
|
+
if (action.entry) {
|
|
150
|
+
const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
|
|
151
|
+
strapi
|
|
152
|
+
});
|
|
153
|
+
if (populatedEntry) {
|
|
154
|
+
const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
|
|
155
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
156
|
+
where: {
|
|
157
|
+
id: action.id
|
|
158
|
+
},
|
|
159
|
+
data: {
|
|
160
|
+
isEntryValid
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
167
|
+
});
|
|
168
|
+
const publishedReleases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
169
|
+
where: {
|
|
170
|
+
status: null,
|
|
171
|
+
releasedAt: {
|
|
172
|
+
$notNull: true
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
utils.async.map(publishedReleases, async (release2) => {
|
|
177
|
+
return strapi.db.query(RELEASE_MODEL_UID).update({
|
|
178
|
+
where: {
|
|
179
|
+
id: release2.id
|
|
180
|
+
},
|
|
181
|
+
data: {
|
|
182
|
+
status: "done"
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
188
|
+
if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
|
|
189
|
+
const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
|
|
190
|
+
(uid) => oldContentTypes[uid]?.options?.draftAndPublish
|
|
191
|
+
);
|
|
192
|
+
const releasesAffected = /* @__PURE__ */ new Set();
|
|
193
|
+
utils.async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
|
|
194
|
+
const oldContentType = oldContentTypes[contentTypeUID];
|
|
195
|
+
const contentType = contentTypes2[contentTypeUID];
|
|
196
|
+
if (!isEqual__default.default(oldContentType?.attributes, contentType?.attributes)) {
|
|
197
|
+
const actions = await strapi.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
198
|
+
where: {
|
|
199
|
+
contentType: contentTypeUID
|
|
200
|
+
},
|
|
201
|
+
populate: {
|
|
202
|
+
entry: true,
|
|
203
|
+
release: true
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
await utils.async.map(actions, async (action) => {
|
|
207
|
+
if (action.entry && action.release) {
|
|
208
|
+
const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
|
|
209
|
+
strapi
|
|
210
|
+
});
|
|
211
|
+
if (populatedEntry) {
|
|
212
|
+
const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
|
|
213
|
+
strapi
|
|
214
|
+
});
|
|
215
|
+
releasesAffected.add(action.release.id);
|
|
216
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
217
|
+
where: {
|
|
218
|
+
id: action.id
|
|
219
|
+
},
|
|
220
|
+
data: {
|
|
221
|
+
isEntryValid
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}).then(() => {
|
|
229
|
+
utils.async.map(releasesAffected, async (releaseId) => {
|
|
230
|
+
return getService("release", { strapi }).updateReleaseStatus(releaseId);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
236
|
+
if (!oldContentTypes) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
for (const uid in contentTypes2) {
|
|
240
|
+
if (!oldContentTypes[uid]) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const oldContentType = oldContentTypes[uid];
|
|
244
|
+
const contentType = contentTypes2[uid];
|
|
245
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
246
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
247
|
+
if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
|
|
248
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
249
|
+
locale: null
|
|
250
|
+
}).where({ contentType: uid }).execute();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
255
|
+
if (!oldContentTypes) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
for (const uid in contentTypes2) {
|
|
259
|
+
if (!oldContentTypes[uid]) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const oldContentType = oldContentTypes[uid];
|
|
263
|
+
const contentType = contentTypes2[uid];
|
|
264
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
265
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
266
|
+
const { getDefaultLocale } = i18nPlugin.service("locales");
|
|
267
|
+
if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
|
|
268
|
+
const defaultLocale = await getDefaultLocale();
|
|
269
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
270
|
+
locale: defaultLocale
|
|
271
|
+
}).where({ contentType: uid }).execute();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
102
275
|
const register = async ({ strapi: strapi2 }) => {
|
|
103
|
-
if (features
|
|
276
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
104
277
|
await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
|
105
|
-
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
|
|
106
|
-
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
|
|
278
|
+
strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
|
|
279
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
280
|
+
}
|
|
281
|
+
if (strapi2.plugin("graphql")) {
|
|
282
|
+
const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
|
|
283
|
+
graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
|
|
284
|
+
graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
|
|
107
285
|
}
|
|
108
286
|
};
|
|
109
|
-
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
110
287
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
111
|
-
if (features
|
|
288
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
289
|
+
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
290
|
+
(uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
|
|
291
|
+
);
|
|
112
292
|
strapi2.db.lifecycles.subscribe({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
293
|
+
models: contentTypesWithDraftAndPublish,
|
|
294
|
+
async afterDelete(event) {
|
|
295
|
+
try {
|
|
296
|
+
const { model, result } = event;
|
|
297
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
298
|
+
const { id } = result;
|
|
299
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
300
|
+
where: {
|
|
301
|
+
actions: {
|
|
302
|
+
target_type: model.uid,
|
|
303
|
+
target_id: id
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
308
|
+
where: {
|
|
309
|
+
target_type: model.uid,
|
|
310
|
+
target_id: id
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
for (const release2 of releases) {
|
|
314
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
121
315
|
}
|
|
122
|
-
}
|
|
316
|
+
}
|
|
317
|
+
} catch (error) {
|
|
318
|
+
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
123
319
|
}
|
|
124
320
|
},
|
|
125
321
|
/**
|
|
@@ -139,20 +335,88 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
139
335
|
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
140
336
|
*/
|
|
141
337
|
async afterDeleteMany(event) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
338
|
+
try {
|
|
339
|
+
const { model, state } = event;
|
|
340
|
+
const entriesToDelete = state.entriesToDelete;
|
|
341
|
+
if (entriesToDelete) {
|
|
342
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
343
|
+
where: {
|
|
344
|
+
actions: {
|
|
345
|
+
target_type: model.uid,
|
|
346
|
+
target_id: {
|
|
347
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
353
|
+
where: {
|
|
354
|
+
target_type: model.uid,
|
|
355
|
+
target_id: {
|
|
356
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
357
|
+
}
|
|
150
358
|
}
|
|
359
|
+
});
|
|
360
|
+
for (const release2 of releases) {
|
|
361
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
151
362
|
}
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {
|
|
365
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
366
|
+
error
|
|
152
367
|
});
|
|
153
368
|
}
|
|
369
|
+
},
|
|
370
|
+
async afterUpdate(event) {
|
|
371
|
+
try {
|
|
372
|
+
const { model, result } = event;
|
|
373
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
374
|
+
const isEntryValid = await getEntryValidStatus(model.uid, result, {
|
|
375
|
+
strapi: strapi2
|
|
376
|
+
});
|
|
377
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
378
|
+
where: {
|
|
379
|
+
target_type: model.uid,
|
|
380
|
+
target_id: result.id
|
|
381
|
+
},
|
|
382
|
+
data: {
|
|
383
|
+
isEntryValid
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
387
|
+
where: {
|
|
388
|
+
actions: {
|
|
389
|
+
target_type: model.uid,
|
|
390
|
+
target_id: result.id
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
for (const release2 of releases) {
|
|
395
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
} catch (error) {
|
|
399
|
+
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
400
|
+
}
|
|
154
401
|
}
|
|
155
402
|
});
|
|
403
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
404
|
+
strapi2.log.error(
|
|
405
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
406
|
+
);
|
|
407
|
+
throw err;
|
|
408
|
+
});
|
|
409
|
+
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
410
|
+
strapi2.webhookStore.addAllowedEvent(key, value);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
const destroy = async ({ strapi: strapi2 }) => {
|
|
415
|
+
const scheduledJobs = getService("scheduling", {
|
|
416
|
+
strapi: strapi2
|
|
417
|
+
}).getAll();
|
|
418
|
+
for (const [, job] of scheduledJobs) {
|
|
419
|
+
job.cancel();
|
|
156
420
|
}
|
|
157
421
|
};
|
|
158
422
|
const schema$1 = {
|
|
@@ -181,6 +445,17 @@ const schema$1 = {
|
|
|
181
445
|
releasedAt: {
|
|
182
446
|
type: "datetime"
|
|
183
447
|
},
|
|
448
|
+
scheduledAt: {
|
|
449
|
+
type: "datetime"
|
|
450
|
+
},
|
|
451
|
+
timezone: {
|
|
452
|
+
type: "string"
|
|
453
|
+
},
|
|
454
|
+
status: {
|
|
455
|
+
type: "enumeration",
|
|
456
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
457
|
+
required: true
|
|
458
|
+
},
|
|
184
459
|
actions: {
|
|
185
460
|
type: "relation",
|
|
186
461
|
relation: "oneToMany",
|
|
@@ -233,6 +508,9 @@ const schema = {
|
|
|
233
508
|
relation: "manyToOne",
|
|
234
509
|
target: RELEASE_MODEL_UID,
|
|
235
510
|
inversedBy: "actions"
|
|
511
|
+
},
|
|
512
|
+
isEntryValid: {
|
|
513
|
+
type: "boolean"
|
|
236
514
|
}
|
|
237
515
|
}
|
|
238
516
|
};
|
|
@@ -243,9 +521,6 @@ const contentTypes = {
|
|
|
243
521
|
release: release$1,
|
|
244
522
|
"release-action": releaseAction$1
|
|
245
523
|
};
|
|
246
|
-
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
247
|
-
return strapi2.plugin("content-releases").service(name);
|
|
248
|
-
};
|
|
249
524
|
const getGroupName = (queryValue) => {
|
|
250
525
|
switch (queryValue) {
|
|
251
526
|
case "contentType":
|
|
@@ -258,419 +533,594 @@ const getGroupName = (queryValue) => {
|
|
|
258
533
|
return "contentType.displayName";
|
|
259
534
|
}
|
|
260
535
|
};
|
|
261
|
-
const createReleaseService = ({ strapi: strapi2 }) =>
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
);
|
|
268
|
-
await Promise.all([
|
|
269
|
-
validatePendingReleasesLimit(),
|
|
270
|
-
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name)
|
|
271
|
-
]);
|
|
272
|
-
return strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
273
|
-
data: releaseWithCreatorFields
|
|
536
|
+
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
537
|
+
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
538
|
+
strapi2.eventHub.emit(event, {
|
|
539
|
+
isPublished,
|
|
540
|
+
error,
|
|
541
|
+
release: release2
|
|
274
542
|
});
|
|
275
|
-
}
|
|
276
|
-
async
|
|
277
|
-
const
|
|
278
|
-
|
|
543
|
+
};
|
|
544
|
+
const publishSingleTypeAction = async (uid, actionType, entryId) => {
|
|
545
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
546
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
547
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
548
|
+
const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
|
|
549
|
+
try {
|
|
550
|
+
if (actionType === "publish") {
|
|
551
|
+
await entityManagerService.publish(entry, uid);
|
|
552
|
+
} else {
|
|
553
|
+
await entityManagerService.unpublish(entry, uid);
|
|
554
|
+
}
|
|
555
|
+
} catch (error) {
|
|
556
|
+
if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
557
|
+
;
|
|
558
|
+
else {
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
|
|
564
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
565
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
566
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
567
|
+
const entriesToPublish = await strapi2.entityService.findMany(uid, {
|
|
568
|
+
filters: {
|
|
569
|
+
id: {
|
|
570
|
+
$in: entriesToPublishIds
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
populate
|
|
279
574
|
});
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
...query,
|
|
285
|
-
populate: {
|
|
286
|
-
actions: {
|
|
287
|
-
// @ts-expect-error Ignore missing properties
|
|
288
|
-
count: true
|
|
575
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
|
|
576
|
+
filters: {
|
|
577
|
+
id: {
|
|
578
|
+
$in: entriestoUnpublishIds
|
|
289
579
|
}
|
|
290
|
-
}
|
|
580
|
+
},
|
|
581
|
+
populate
|
|
291
582
|
});
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
583
|
+
if (entriesToPublish.length > 0) {
|
|
584
|
+
await entityManagerService.publishMany(entriesToPublish, uid);
|
|
585
|
+
}
|
|
586
|
+
if (entriesToUnpublish.length > 0) {
|
|
587
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, uid);
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
const getFormattedActions = async (releaseId) => {
|
|
591
|
+
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
295
592
|
where: {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
target_id: entryId
|
|
299
|
-
},
|
|
300
|
-
releasedAt: {
|
|
301
|
-
$null: true
|
|
593
|
+
release: {
|
|
594
|
+
id: releaseId
|
|
302
595
|
}
|
|
303
596
|
},
|
|
304
597
|
populate: {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
where: {
|
|
308
|
-
target_type: contentTypeUid,
|
|
309
|
-
target_id: entryId
|
|
310
|
-
}
|
|
598
|
+
entry: {
|
|
599
|
+
fields: ["id"]
|
|
311
600
|
}
|
|
312
601
|
}
|
|
313
602
|
});
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
603
|
+
if (actions.length === 0) {
|
|
604
|
+
throw new utils.errors.ValidationError("No entries to publish");
|
|
605
|
+
}
|
|
606
|
+
const collectionTypeActions = {};
|
|
607
|
+
const singleTypeActions = [];
|
|
608
|
+
for (const action of actions) {
|
|
609
|
+
const contentTypeUid = action.contentType;
|
|
610
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
611
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
612
|
+
collectionTypeActions[contentTypeUid] = {
|
|
613
|
+
entriesToPublishIds: [],
|
|
614
|
+
entriesToUnpublishIds: []
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
if (action.type === "publish") {
|
|
618
|
+
collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
|
|
619
|
+
} else {
|
|
620
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
621
|
+
}
|
|
622
|
+
} else {
|
|
623
|
+
singleTypeActions.push({
|
|
624
|
+
uid: contentTypeUid,
|
|
625
|
+
action: action.type,
|
|
626
|
+
id: action.entry.id
|
|
627
|
+
});
|
|
322
628
|
}
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
629
|
+
}
|
|
630
|
+
return { collectionTypeActions, singleTypeActions };
|
|
631
|
+
};
|
|
632
|
+
return {
|
|
633
|
+
async create(releaseData, { user }) {
|
|
634
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
|
|
635
|
+
const {
|
|
636
|
+
validatePendingReleasesLimit,
|
|
637
|
+
validateUniqueNameForPendingRelease,
|
|
638
|
+
validateScheduledAtIsLaterThanNow
|
|
639
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
640
|
+
await Promise.all([
|
|
641
|
+
validatePendingReleasesLimit(),
|
|
642
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
643
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
644
|
+
]);
|
|
645
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).create({
|
|
646
|
+
data: {
|
|
647
|
+
...releaseWithCreatorFields,
|
|
648
|
+
status: "empty"
|
|
335
649
|
}
|
|
650
|
+
});
|
|
651
|
+
if (releaseWithCreatorFields.scheduledAt) {
|
|
652
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
653
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
336
654
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
655
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
656
|
+
return release2;
|
|
657
|
+
},
|
|
658
|
+
async findOne(id, query = {}) {
|
|
659
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query);
|
|
660
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
661
|
+
...dbQuery,
|
|
662
|
+
where: { id }
|
|
663
|
+
});
|
|
664
|
+
return release2;
|
|
665
|
+
},
|
|
666
|
+
findPage(query) {
|
|
667
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
|
|
668
|
+
return strapi2.db.query(RELEASE_MODEL_UID).findPage({
|
|
669
|
+
...dbQuery,
|
|
670
|
+
populate: {
|
|
671
|
+
actions: {
|
|
672
|
+
count: true
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
},
|
|
677
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
678
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
679
|
+
where: {
|
|
680
|
+
actions: {
|
|
681
|
+
target_type: contentTypeUid,
|
|
682
|
+
target_id: entryId
|
|
683
|
+
},
|
|
684
|
+
releasedAt: {
|
|
685
|
+
$null: true
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
populate: {
|
|
689
|
+
// Filter the action to get only the content type entry
|
|
690
|
+
actions: {
|
|
691
|
+
where: {
|
|
692
|
+
target_type: contentTypeUid,
|
|
693
|
+
target_id: entryId
|
|
344
694
|
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
return releases.map((release2) => {
|
|
699
|
+
if (release2.actions?.length) {
|
|
700
|
+
const [actionForEntry] = release2.actions;
|
|
701
|
+
delete release2.actions;
|
|
702
|
+
return {
|
|
703
|
+
...release2,
|
|
704
|
+
action: actionForEntry
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
return release2;
|
|
708
|
+
});
|
|
709
|
+
},
|
|
710
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
711
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
712
|
+
where: {
|
|
713
|
+
releasedAt: {
|
|
714
|
+
$null: true
|
|
345
715
|
},
|
|
346
|
-
{
|
|
347
|
-
|
|
716
|
+
actions: {
|
|
717
|
+
target_type: contentTypeUid,
|
|
718
|
+
target_id: entryId
|
|
348
719
|
}
|
|
349
|
-
],
|
|
350
|
-
releasedAt: {
|
|
351
|
-
$null: true
|
|
352
720
|
}
|
|
721
|
+
});
|
|
722
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
723
|
+
where: {
|
|
724
|
+
$or: [
|
|
725
|
+
{
|
|
726
|
+
id: {
|
|
727
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
actions: null
|
|
732
|
+
}
|
|
733
|
+
],
|
|
734
|
+
releasedAt: {
|
|
735
|
+
$null: true
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
return releases.map((release2) => {
|
|
740
|
+
if (release2.actions?.length) {
|
|
741
|
+
const [actionForEntry] = release2.actions;
|
|
742
|
+
delete release2.actions;
|
|
743
|
+
return {
|
|
744
|
+
...release2,
|
|
745
|
+
action: actionForEntry
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
return release2;
|
|
749
|
+
});
|
|
750
|
+
},
|
|
751
|
+
async update(id, releaseData, { user }) {
|
|
752
|
+
const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(
|
|
753
|
+
releaseData
|
|
754
|
+
);
|
|
755
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
756
|
+
"release-validation",
|
|
757
|
+
{ strapi: strapi2 }
|
|
758
|
+
);
|
|
759
|
+
await Promise.all([
|
|
760
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
761
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
762
|
+
]);
|
|
763
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id } });
|
|
764
|
+
if (!release2) {
|
|
765
|
+
throw new utils.errors.NotFoundError(`No release found for id ${id}`);
|
|
353
766
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (release2.actions?.length) {
|
|
357
|
-
const [actionForEntry] = release2.actions;
|
|
358
|
-
delete release2.actions;
|
|
359
|
-
return {
|
|
360
|
-
...release2,
|
|
361
|
-
action: actionForEntry
|
|
362
|
-
};
|
|
767
|
+
if (release2.releasedAt) {
|
|
768
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
363
769
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (release2.releasedAt) {
|
|
374
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
375
|
-
}
|
|
376
|
-
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
377
|
-
/*
|
|
378
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
379
|
-
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
380
|
-
*/
|
|
381
|
-
// @ts-expect-error see above
|
|
382
|
-
data: releaseWithCreatorFields
|
|
383
|
-
});
|
|
384
|
-
return updatedRelease;
|
|
385
|
-
},
|
|
386
|
-
async createAction(releaseId, action) {
|
|
387
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
388
|
-
strapi: strapi2
|
|
389
|
-
});
|
|
390
|
-
await Promise.all([
|
|
391
|
-
validateEntryContentType(action.entry.contentType),
|
|
392
|
-
validateUniqueEntry(releaseId, action)
|
|
393
|
-
]);
|
|
394
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
395
|
-
if (!release2) {
|
|
396
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
397
|
-
}
|
|
398
|
-
if (release2.releasedAt) {
|
|
399
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
400
|
-
}
|
|
401
|
-
const { entry, type } = action;
|
|
402
|
-
return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
403
|
-
data: {
|
|
404
|
-
type,
|
|
405
|
-
contentType: entry.contentType,
|
|
406
|
-
locale: entry.locale,
|
|
407
|
-
entry: {
|
|
408
|
-
id: entry.id,
|
|
409
|
-
__type: entry.contentType,
|
|
410
|
-
__pivot: { field: "entry" }
|
|
411
|
-
},
|
|
412
|
-
release: releaseId
|
|
413
|
-
},
|
|
414
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
415
|
-
});
|
|
416
|
-
},
|
|
417
|
-
async findActions(releaseId, query) {
|
|
418
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
419
|
-
fields: ["id"]
|
|
420
|
-
});
|
|
421
|
-
if (!release2) {
|
|
422
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
423
|
-
}
|
|
424
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
425
|
-
...query,
|
|
426
|
-
populate: {
|
|
427
|
-
entry: {
|
|
428
|
-
populate: "*"
|
|
429
|
-
}
|
|
430
|
-
},
|
|
431
|
-
filters: {
|
|
432
|
-
release: releaseId
|
|
770
|
+
const updatedRelease = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
771
|
+
where: { id },
|
|
772
|
+
data: releaseWithCreatorFields
|
|
773
|
+
});
|
|
774
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
775
|
+
if (releaseData.scheduledAt) {
|
|
776
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
777
|
+
} else if (release2.scheduledAt) {
|
|
778
|
+
schedulingService.cancel(id);
|
|
433
779
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
780
|
+
this.updateReleaseStatus(id);
|
|
781
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
782
|
+
return updatedRelease;
|
|
783
|
+
},
|
|
784
|
+
async createAction(releaseId, action) {
|
|
785
|
+
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
786
|
+
strapi: strapi2
|
|
787
|
+
});
|
|
788
|
+
await Promise.all([
|
|
789
|
+
validateEntryContentType(action.entry.contentType),
|
|
790
|
+
validateUniqueEntry(releaseId, action)
|
|
791
|
+
]);
|
|
792
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
|
|
793
|
+
if (!release2) {
|
|
794
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
443
795
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
},
|
|
465
|
-
async getLocalesDataForActions() {
|
|
466
|
-
if (!strapi2.plugin("i18n")) {
|
|
467
|
-
return {};
|
|
468
|
-
}
|
|
469
|
-
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
470
|
-
return allLocales.reduce((acc, locale) => {
|
|
471
|
-
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
472
|
-
return acc;
|
|
473
|
-
}, {});
|
|
474
|
-
},
|
|
475
|
-
async getContentTypesDataForActions(contentTypesUids) {
|
|
476
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
477
|
-
const contentTypesData = {};
|
|
478
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
479
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
480
|
-
uid: contentTypeUid
|
|
796
|
+
if (release2.releasedAt) {
|
|
797
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
798
|
+
}
|
|
799
|
+
const { entry, type } = action;
|
|
800
|
+
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
801
|
+
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
802
|
+
const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
|
|
803
|
+
data: {
|
|
804
|
+
type,
|
|
805
|
+
contentType: entry.contentType,
|
|
806
|
+
locale: entry.locale,
|
|
807
|
+
isEntryValid,
|
|
808
|
+
entry: {
|
|
809
|
+
id: entry.id,
|
|
810
|
+
__type: entry.contentType,
|
|
811
|
+
__pivot: { field: "entry" }
|
|
812
|
+
},
|
|
813
|
+
release: releaseId
|
|
814
|
+
},
|
|
815
|
+
populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
|
|
481
816
|
});
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
acc.push(action.contentType);
|
|
817
|
+
this.updateReleaseStatus(releaseId);
|
|
818
|
+
return releaseAction2;
|
|
819
|
+
},
|
|
820
|
+
async findActions(releaseId, query) {
|
|
821
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
822
|
+
where: { id: releaseId },
|
|
823
|
+
select: ["id"]
|
|
824
|
+
});
|
|
825
|
+
if (!release2) {
|
|
826
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
493
827
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
828
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
829
|
+
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
830
|
+
...dbQuery,
|
|
831
|
+
populate: {
|
|
832
|
+
entry: {
|
|
833
|
+
populate: "*"
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
where: {
|
|
837
|
+
release: releaseId
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
},
|
|
841
|
+
async countActions(query) {
|
|
842
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
843
|
+
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
|
|
844
|
+
},
|
|
845
|
+
async groupActions(actions, groupBy) {
|
|
846
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
847
|
+
if (!acc.includes(action.contentType)) {
|
|
848
|
+
acc.push(action.contentType);
|
|
849
|
+
}
|
|
499
850
|
return acc;
|
|
500
|
-
},
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
851
|
+
}, []);
|
|
852
|
+
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
|
|
853
|
+
contentTypeUids
|
|
854
|
+
);
|
|
855
|
+
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
856
|
+
const formattedData = actions.map((action) => {
|
|
857
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
858
|
+
return {
|
|
859
|
+
...action,
|
|
860
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
861
|
+
contentType: {
|
|
862
|
+
displayName,
|
|
863
|
+
mainFieldValue: action.entry[mainField],
|
|
864
|
+
uid: action.contentType
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
});
|
|
868
|
+
const groupName = getGroupName(groupBy);
|
|
869
|
+
return ___default.default.groupBy(groupName)(formattedData);
|
|
870
|
+
},
|
|
871
|
+
async getLocalesDataForActions() {
|
|
872
|
+
if (!strapi2.plugin("i18n")) {
|
|
873
|
+
return {};
|
|
874
|
+
}
|
|
875
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
876
|
+
return allLocales.reduce((acc, locale) => {
|
|
877
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
511
878
|
return acc;
|
|
512
|
-
},
|
|
513
|
-
|
|
514
|
-
)
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
879
|
+
}, {});
|
|
880
|
+
},
|
|
881
|
+
async getContentTypesDataForActions(contentTypesUids) {
|
|
882
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
883
|
+
const contentTypesData = {};
|
|
884
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
885
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
886
|
+
uid: contentTypeUid
|
|
887
|
+
});
|
|
888
|
+
contentTypesData[contentTypeUid] = {
|
|
889
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
890
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
891
|
+
};
|
|
523
892
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
}
|
|
531
|
-
await strapi2.db.transaction(async () => {
|
|
532
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
533
|
-
where: {
|
|
534
|
-
id: {
|
|
535
|
-
$in: release2.actions.map((action) => action.id)
|
|
536
|
-
}
|
|
893
|
+
return contentTypesData;
|
|
894
|
+
},
|
|
895
|
+
getContentTypeModelsFromActions(actions) {
|
|
896
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
897
|
+
if (!acc.includes(action.contentType)) {
|
|
898
|
+
acc.push(action.contentType);
|
|
537
899
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
900
|
+
return acc;
|
|
901
|
+
}, []);
|
|
902
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
903
|
+
(acc, contentTypeUid) => {
|
|
904
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
905
|
+
return acc;
|
|
906
|
+
},
|
|
907
|
+
{}
|
|
908
|
+
);
|
|
909
|
+
return contentTypeModelsMap;
|
|
910
|
+
},
|
|
911
|
+
async getAllComponents() {
|
|
912
|
+
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
913
|
+
const components = await contentManagerComponentsService.findAllComponents();
|
|
914
|
+
const componentsMap = components.reduce(
|
|
915
|
+
(acc, component) => {
|
|
916
|
+
acc[component.uid] = component;
|
|
917
|
+
return acc;
|
|
918
|
+
},
|
|
919
|
+
{}
|
|
920
|
+
);
|
|
921
|
+
return componentsMap;
|
|
922
|
+
},
|
|
923
|
+
async delete(releaseId) {
|
|
924
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
925
|
+
where: { id: releaseId },
|
|
548
926
|
populate: {
|
|
549
927
|
actions: {
|
|
550
|
-
|
|
551
|
-
entry: {
|
|
552
|
-
fields: ["id"]
|
|
553
|
-
}
|
|
554
|
-
}
|
|
928
|
+
select: ["id"]
|
|
555
929
|
}
|
|
556
930
|
}
|
|
931
|
+
});
|
|
932
|
+
if (!release2) {
|
|
933
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
557
934
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
561
|
-
}
|
|
562
|
-
if (releaseWithPopulatedActionEntries.releasedAt) {
|
|
563
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
564
|
-
}
|
|
565
|
-
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
566
|
-
throw new utils.errors.ValidationError("No entries to publish");
|
|
567
|
-
}
|
|
568
|
-
const actions = {};
|
|
569
|
-
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
570
|
-
const contentTypeUid = action.contentType;
|
|
571
|
-
if (!actions[contentTypeUid]) {
|
|
572
|
-
actions[contentTypeUid] = {
|
|
573
|
-
entriestoPublishIds: [],
|
|
574
|
-
entriesToUnpublishIds: []
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
if (action.type === "publish") {
|
|
578
|
-
actions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
579
|
-
} else {
|
|
580
|
-
actions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
935
|
+
if (release2.releasedAt) {
|
|
936
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
581
937
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const { entriestoPublishIds, entriesToUnpublishIds } = actions[contentTypeUid];
|
|
589
|
-
const entriesToPublish = await strapi2.entityService.findMany(
|
|
590
|
-
contentTypeUid,
|
|
591
|
-
{
|
|
592
|
-
filters: {
|
|
593
|
-
id: {
|
|
594
|
-
$in: entriestoPublishIds
|
|
595
|
-
}
|
|
596
|
-
},
|
|
597
|
-
populate
|
|
938
|
+
await strapi2.db.transaction(async () => {
|
|
939
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
940
|
+
where: {
|
|
941
|
+
id: {
|
|
942
|
+
$in: release2.actions.map((action) => action.id)
|
|
943
|
+
}
|
|
598
944
|
}
|
|
599
|
-
);
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
filters: {
|
|
604
|
-
id: {
|
|
605
|
-
$in: entriesToUnpublishIds
|
|
606
|
-
}
|
|
607
|
-
},
|
|
608
|
-
populate
|
|
945
|
+
});
|
|
946
|
+
await strapi2.db.query(RELEASE_MODEL_UID).delete({
|
|
947
|
+
where: {
|
|
948
|
+
id: releaseId
|
|
609
949
|
}
|
|
610
|
-
);
|
|
611
|
-
|
|
612
|
-
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
if (release2.scheduledAt) {
|
|
953
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
954
|
+
await schedulingService.cancel(release2.id);
|
|
955
|
+
}
|
|
956
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
957
|
+
return release2;
|
|
958
|
+
},
|
|
959
|
+
async publish(releaseId) {
|
|
960
|
+
const {
|
|
961
|
+
release: release2,
|
|
962
|
+
error
|
|
963
|
+
} = await strapi2.db.transaction(async ({ trx }) => {
|
|
964
|
+
const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
|
|
965
|
+
if (!lockedRelease) {
|
|
966
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
613
967
|
}
|
|
614
|
-
if (
|
|
615
|
-
|
|
968
|
+
if (lockedRelease.releasedAt) {
|
|
969
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
616
970
|
}
|
|
971
|
+
if (lockedRelease.status === "failed") {
|
|
972
|
+
throw new utils.errors.ValidationError("Release failed to publish");
|
|
973
|
+
}
|
|
974
|
+
try {
|
|
975
|
+
strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
|
|
976
|
+
const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
|
|
977
|
+
releaseId
|
|
978
|
+
);
|
|
979
|
+
await strapi2.db.transaction(async () => {
|
|
980
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
981
|
+
await publishSingleTypeAction(uid, action, id);
|
|
982
|
+
}
|
|
983
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
984
|
+
const uid = contentTypeUid;
|
|
985
|
+
await publishCollectionTypeAction(
|
|
986
|
+
uid,
|
|
987
|
+
collectionTypeActions[uid].entriesToPublishIds,
|
|
988
|
+
collectionTypeActions[uid].entriesToUnpublishIds
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
993
|
+
where: {
|
|
994
|
+
id: releaseId
|
|
995
|
+
},
|
|
996
|
+
data: {
|
|
997
|
+
status: "done",
|
|
998
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
1002
|
+
isPublished: true,
|
|
1003
|
+
release: release22
|
|
1004
|
+
});
|
|
1005
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
1006
|
+
return { release: release22, error: null };
|
|
1007
|
+
} catch (error2) {
|
|
1008
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
1009
|
+
isPublished: false,
|
|
1010
|
+
error: error2
|
|
1011
|
+
});
|
|
1012
|
+
await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
|
|
1013
|
+
status: "failed"
|
|
1014
|
+
}).transacting(trx).execute();
|
|
1015
|
+
return {
|
|
1016
|
+
release: null,
|
|
1017
|
+
error: error2
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
if (error instanceof Error) {
|
|
1022
|
+
throw error;
|
|
617
1023
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
1024
|
+
return release2;
|
|
1025
|
+
},
|
|
1026
|
+
async updateAction(actionId, releaseId, update) {
|
|
1027
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1028
|
+
where: {
|
|
1029
|
+
id: actionId,
|
|
1030
|
+
release: {
|
|
1031
|
+
id: releaseId,
|
|
1032
|
+
releasedAt: {
|
|
1033
|
+
$null: true
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
data: update
|
|
1038
|
+
});
|
|
1039
|
+
if (!updatedAction) {
|
|
1040
|
+
throw new utils.errors.NotFoundError(
|
|
1041
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1042
|
+
);
|
|
626
1043
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
1044
|
+
return updatedAction;
|
|
1045
|
+
},
|
|
1046
|
+
async deleteAction(actionId, releaseId) {
|
|
1047
|
+
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
1048
|
+
where: {
|
|
1049
|
+
id: actionId,
|
|
1050
|
+
release: {
|
|
1051
|
+
id: releaseId,
|
|
1052
|
+
releasedAt: {
|
|
1053
|
+
$null: true
|
|
1054
|
+
}
|
|
638
1055
|
}
|
|
639
1056
|
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
);
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
release: {
|
|
655
|
-
id: releaseId,
|
|
656
|
-
releasedAt: {
|
|
657
|
-
$null: true
|
|
1057
|
+
});
|
|
1058
|
+
if (!deletedAction) {
|
|
1059
|
+
throw new utils.errors.NotFoundError(
|
|
1060
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
this.updateReleaseStatus(releaseId);
|
|
1064
|
+
return deletedAction;
|
|
1065
|
+
},
|
|
1066
|
+
async updateReleaseStatus(releaseId) {
|
|
1067
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
1068
|
+
this.countActions({
|
|
1069
|
+
filters: {
|
|
1070
|
+
release: releaseId
|
|
658
1071
|
}
|
|
1072
|
+
}),
|
|
1073
|
+
this.countActions({
|
|
1074
|
+
filters: {
|
|
1075
|
+
release: releaseId,
|
|
1076
|
+
isEntryValid: false
|
|
1077
|
+
}
|
|
1078
|
+
})
|
|
1079
|
+
]);
|
|
1080
|
+
if (totalActions > 0) {
|
|
1081
|
+
if (invalidActions > 0) {
|
|
1082
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1083
|
+
where: {
|
|
1084
|
+
id: releaseId
|
|
1085
|
+
},
|
|
1086
|
+
data: {
|
|
1087
|
+
status: "blocked"
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
659
1090
|
}
|
|
1091
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1092
|
+
where: {
|
|
1093
|
+
id: releaseId
|
|
1094
|
+
},
|
|
1095
|
+
data: {
|
|
1096
|
+
status: "ready"
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
660
1099
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
1100
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1101
|
+
where: {
|
|
1102
|
+
id: releaseId
|
|
1103
|
+
},
|
|
1104
|
+
data: {
|
|
1105
|
+
status: "empty"
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
666
1108
|
}
|
|
667
|
-
|
|
1109
|
+
};
|
|
1110
|
+
};
|
|
1111
|
+
class AlreadyOnReleaseError extends utils.errors.ApplicationError {
|
|
1112
|
+
constructor(message) {
|
|
1113
|
+
super(message);
|
|
1114
|
+
this.name = "AlreadyOnReleaseError";
|
|
668
1115
|
}
|
|
669
|
-
}
|
|
1116
|
+
}
|
|
670
1117
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
671
1118
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
672
|
-
const release2 = await strapi2.
|
|
673
|
-
|
|
1119
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
1120
|
+
where: {
|
|
1121
|
+
id: releaseId
|
|
1122
|
+
},
|
|
1123
|
+
populate: { actions: { populate: { entry: { select: ["id"] } } } }
|
|
674
1124
|
});
|
|
675
1125
|
if (!release2) {
|
|
676
1126
|
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
@@ -679,7 +1129,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
679
1129
|
(action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
|
|
680
1130
|
);
|
|
681
1131
|
if (isEntryInRelease) {
|
|
682
|
-
throw new
|
|
1132
|
+
throw new AlreadyOnReleaseError(
|
|
683
1133
|
`Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
|
|
684
1134
|
);
|
|
685
1135
|
}
|
|
@@ -696,10 +1146,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
696
1146
|
}
|
|
697
1147
|
},
|
|
698
1148
|
async validatePendingReleasesLimit() {
|
|
699
|
-
const maximumPendingReleases = (
|
|
700
|
-
// @ts-expect-error - options is not typed into features
|
|
701
|
-
EE__default.default.features.get("cms-content-releases")?.options?.maximumReleases || 3
|
|
702
|
-
);
|
|
1149
|
+
const maximumPendingReleases = strapi2.ee.features.get("cms-content-releases")?.options?.maximumReleases || 3;
|
|
703
1150
|
const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
|
|
704
1151
|
filters: {
|
|
705
1152
|
releasedAt: {
|
|
@@ -711,27 +1158,103 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
711
1158
|
throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
|
|
712
1159
|
}
|
|
713
1160
|
},
|
|
714
|
-
async validateUniqueNameForPendingRelease(name) {
|
|
715
|
-
const pendingReleases = await strapi2.
|
|
716
|
-
|
|
1161
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
1162
|
+
const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1163
|
+
where: {
|
|
717
1164
|
releasedAt: {
|
|
718
1165
|
$null: true
|
|
719
1166
|
},
|
|
720
|
-
name
|
|
1167
|
+
name,
|
|
1168
|
+
...id && { id: { $ne: id } }
|
|
721
1169
|
}
|
|
722
1170
|
});
|
|
723
1171
|
const isNameUnique = pendingReleases.length === 0;
|
|
724
1172
|
if (!isNameUnique) {
|
|
725
1173
|
throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
|
|
726
1174
|
}
|
|
1175
|
+
},
|
|
1176
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
1177
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
1178
|
+
throw new utils.errors.ValidationError("Scheduled at must be later than now");
|
|
1179
|
+
}
|
|
727
1180
|
}
|
|
728
1181
|
});
|
|
1182
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
1183
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
1184
|
+
return {
|
|
1185
|
+
async set(releaseId, scheduleDate) {
|
|
1186
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
1187
|
+
if (!release2) {
|
|
1188
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1189
|
+
}
|
|
1190
|
+
const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
|
|
1191
|
+
try {
|
|
1192
|
+
await getService("release").publish(releaseId);
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
}
|
|
1195
|
+
this.cancel(releaseId);
|
|
1196
|
+
});
|
|
1197
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1198
|
+
this.cancel(releaseId);
|
|
1199
|
+
}
|
|
1200
|
+
scheduledJobs.set(releaseId, job);
|
|
1201
|
+
return scheduledJobs;
|
|
1202
|
+
},
|
|
1203
|
+
cancel(releaseId) {
|
|
1204
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1205
|
+
scheduledJobs.get(releaseId).cancel();
|
|
1206
|
+
scheduledJobs.delete(releaseId);
|
|
1207
|
+
}
|
|
1208
|
+
return scheduledJobs;
|
|
1209
|
+
},
|
|
1210
|
+
getAll() {
|
|
1211
|
+
return scheduledJobs;
|
|
1212
|
+
},
|
|
1213
|
+
/**
|
|
1214
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
1215
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
1216
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
1217
|
+
*/
|
|
1218
|
+
async syncFromDatabase() {
|
|
1219
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1220
|
+
where: {
|
|
1221
|
+
scheduledAt: {
|
|
1222
|
+
$gte: /* @__PURE__ */ new Date()
|
|
1223
|
+
},
|
|
1224
|
+
releasedAt: null
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
for (const release2 of releases) {
|
|
1228
|
+
this.set(release2.id, release2.scheduledAt);
|
|
1229
|
+
}
|
|
1230
|
+
return scheduledJobs;
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
};
|
|
729
1234
|
const services = {
|
|
730
1235
|
release: createReleaseService,
|
|
731
|
-
"release-validation": createReleaseValidationService
|
|
1236
|
+
"release-validation": createReleaseValidationService,
|
|
1237
|
+
scheduling: createSchedulingService
|
|
732
1238
|
};
|
|
733
1239
|
const RELEASE_SCHEMA = yup__namespace.object().shape({
|
|
734
|
-
name: yup__namespace.string().trim().required()
|
|
1240
|
+
name: yup__namespace.string().trim().required(),
|
|
1241
|
+
scheduledAt: yup__namespace.string().nullable(),
|
|
1242
|
+
isScheduled: yup__namespace.boolean().optional(),
|
|
1243
|
+
time: yup__namespace.string().when("isScheduled", {
|
|
1244
|
+
is: true,
|
|
1245
|
+
then: yup__namespace.string().trim().required(),
|
|
1246
|
+
otherwise: yup__namespace.string().nullable()
|
|
1247
|
+
}),
|
|
1248
|
+
timezone: yup__namespace.string().when("isScheduled", {
|
|
1249
|
+
is: true,
|
|
1250
|
+
then: yup__namespace.string().required().nullable(),
|
|
1251
|
+
otherwise: yup__namespace.string().nullable()
|
|
1252
|
+
}),
|
|
1253
|
+
date: yup__namespace.string().when("isScheduled", {
|
|
1254
|
+
is: true,
|
|
1255
|
+
then: yup__namespace.string().required().nullable(),
|
|
1256
|
+
otherwise: yup__namespace.string().nullable()
|
|
1257
|
+
})
|
|
735
1258
|
}).required().noUnknown();
|
|
736
1259
|
const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
|
|
737
1260
|
const releaseController = {
|
|
@@ -764,26 +1287,30 @@ const releaseController = {
|
|
|
764
1287
|
}
|
|
765
1288
|
};
|
|
766
1289
|
});
|
|
767
|
-
|
|
1290
|
+
const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
|
|
1291
|
+
where: {
|
|
1292
|
+
releasedAt: null
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
768
1296
|
}
|
|
769
1297
|
},
|
|
770
1298
|
async findOne(ctx) {
|
|
771
1299
|
const id = ctx.params.id;
|
|
772
1300
|
const releaseService = getService("release", { strapi });
|
|
773
1301
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
});
|
|
778
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
1302
|
+
if (!release2) {
|
|
1303
|
+
throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
|
|
1304
|
+
}
|
|
779
1305
|
const count = await releaseService.countActions({
|
|
780
1306
|
filters: {
|
|
781
1307
|
release: id
|
|
782
1308
|
}
|
|
783
1309
|
});
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
1310
|
+
const sanitizedRelease = {
|
|
1311
|
+
...release2,
|
|
1312
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
1313
|
+
};
|
|
787
1314
|
const data = {
|
|
788
1315
|
...sanitizedRelease,
|
|
789
1316
|
actions: {
|
|
@@ -836,8 +1363,27 @@ const releaseController = {
|
|
|
836
1363
|
const id = ctx.params.id;
|
|
837
1364
|
const releaseService = getService("release", { strapi });
|
|
838
1365
|
const release2 = await releaseService.publish(id, { user });
|
|
1366
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1367
|
+
releaseService.countActions({
|
|
1368
|
+
filters: {
|
|
1369
|
+
release: id,
|
|
1370
|
+
type: "publish"
|
|
1371
|
+
}
|
|
1372
|
+
}),
|
|
1373
|
+
releaseService.countActions({
|
|
1374
|
+
filters: {
|
|
1375
|
+
release: id,
|
|
1376
|
+
type: "unpublish"
|
|
1377
|
+
}
|
|
1378
|
+
})
|
|
1379
|
+
]);
|
|
839
1380
|
ctx.body = {
|
|
840
|
-
data: release2
|
|
1381
|
+
data: release2,
|
|
1382
|
+
meta: {
|
|
1383
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1384
|
+
totalPublishedEntries: countPublishActions,
|
|
1385
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1386
|
+
}
|
|
841
1387
|
};
|
|
842
1388
|
}
|
|
843
1389
|
};
|
|
@@ -864,6 +1410,38 @@ const releaseActionController = {
|
|
|
864
1410
|
data: releaseAction2
|
|
865
1411
|
};
|
|
866
1412
|
},
|
|
1413
|
+
async createMany(ctx) {
|
|
1414
|
+
const releaseId = ctx.params.releaseId;
|
|
1415
|
+
const releaseActionsArgs = ctx.request.body;
|
|
1416
|
+
await Promise.all(
|
|
1417
|
+
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
|
1418
|
+
);
|
|
1419
|
+
const releaseService = getService("release", { strapi });
|
|
1420
|
+
const releaseActions = await strapi.db.transaction(async () => {
|
|
1421
|
+
const releaseActions2 = await Promise.all(
|
|
1422
|
+
releaseActionsArgs.map(async (releaseActionArgs) => {
|
|
1423
|
+
try {
|
|
1424
|
+
const action = await releaseService.createAction(releaseId, releaseActionArgs);
|
|
1425
|
+
return action;
|
|
1426
|
+
} catch (error) {
|
|
1427
|
+
if (error instanceof AlreadyOnReleaseError) {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
throw error;
|
|
1431
|
+
}
|
|
1432
|
+
})
|
|
1433
|
+
);
|
|
1434
|
+
return releaseActions2;
|
|
1435
|
+
});
|
|
1436
|
+
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
|
1437
|
+
ctx.body = {
|
|
1438
|
+
data: newReleaseActions,
|
|
1439
|
+
meta: {
|
|
1440
|
+
entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
|
|
1441
|
+
totalEntries: releaseActions.length
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
},
|
|
867
1445
|
async findMany(ctx) {
|
|
868
1446
|
const releaseId = ctx.params.releaseId;
|
|
869
1447
|
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
|
@@ -887,7 +1465,7 @@ const releaseActionController = {
|
|
|
887
1465
|
acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
|
|
888
1466
|
return acc;
|
|
889
1467
|
}, {});
|
|
890
|
-
const sanitizedResults = await utils.
|
|
1468
|
+
const sanitizedResults = await utils.async.map(results, async (action) => ({
|
|
891
1469
|
...action,
|
|
892
1470
|
entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
|
|
893
1471
|
}));
|
|
@@ -1049,6 +1627,22 @@ const releaseAction = {
|
|
|
1049
1627
|
]
|
|
1050
1628
|
}
|
|
1051
1629
|
},
|
|
1630
|
+
{
|
|
1631
|
+
method: "POST",
|
|
1632
|
+
path: "/:releaseId/actions/bulk",
|
|
1633
|
+
handler: "release-action.createMany",
|
|
1634
|
+
config: {
|
|
1635
|
+
policies: [
|
|
1636
|
+
"admin::isAuthenticatedAdmin",
|
|
1637
|
+
{
|
|
1638
|
+
name: "admin::hasPermissions",
|
|
1639
|
+
config: {
|
|
1640
|
+
actions: ["plugin::content-releases.create-action"]
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
]
|
|
1644
|
+
}
|
|
1645
|
+
},
|
|
1052
1646
|
{
|
|
1053
1647
|
method: "GET",
|
|
1054
1648
|
path: "/:releaseId/actions",
|
|
@@ -1103,12 +1697,12 @@ const routes = {
|
|
|
1103
1697
|
release,
|
|
1104
1698
|
"release-action": releaseAction
|
|
1105
1699
|
};
|
|
1106
|
-
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
1107
1700
|
const getPlugin = () => {
|
|
1108
|
-
if (features.isEnabled("cms-content-releases")) {
|
|
1701
|
+
if (strapi.ee.features.isEnabled("cms-content-releases")) {
|
|
1109
1702
|
return {
|
|
1110
1703
|
register,
|
|
1111
1704
|
bootstrap,
|
|
1705
|
+
destroy,
|
|
1112
1706
|
contentTypes,
|
|
1113
1707
|
services,
|
|
1114
1708
|
controllers,
|
|
@@ -1116,6 +1710,9 @@ const getPlugin = () => {
|
|
|
1116
1710
|
};
|
|
1117
1711
|
}
|
|
1118
1712
|
return {
|
|
1713
|
+
// Always return register, it handles its own feature check
|
|
1714
|
+
register,
|
|
1715
|
+
// Always return contentTypes to avoid losing data when the feature is disabled
|
|
1119
1716
|
contentTypes
|
|
1120
1717
|
};
|
|
1121
1718
|
};
|