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